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

© Copyright 2024 by Mark DiVecchio
Active Engine List © Copyright 2026 by Carl K. Ross (notated as CKR)

    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
//---------------------------------------------------------------------------
/*
Forms which are auto created:

MainForm
DB
NumberQuery
Command_Query_Form
DetectedRFIDTags
BlockNameQuery
DropDownQuery
PopOutEngineList
LashUp
PopOutLashUpList
RouteScene
PopOutRouteSceneList

All of the rest of the forms are manually created.

Linker
	[x] Create debug information
	[ ] Use dynamic RTL
	[x] Use debug Libraries

Packages
	[ ] Build with runtime packages
//---------------------------------------------------------------------------
There may be a bug in Borland C++ Builder which causes a stack overflow
if the overridden constructor only has one additional parameter, an int.

All overrider constructors should have at least two additional int paramters.
//---------------------------------------------------------------------------
A bug in BCB4 and 5 (fixed in BCB 6) caused trouble in starting of threads.

According to Remy Lebeau(TeamB) in this post:

"If you set CreateSuspended to False, the thread will begin running automatically,
AFTER all constructors have been called and exited first (early versions
had a bug in this area, which was finally fixed in D/CB v6."

Then he followed up in this post:

"That is the bug I referred to which was fixed in CB/Delphi v6. In earlier versions,
TThread's constructor did not take descendant constructors into account, so it
allowed the thread to begin running immediately, before descendant constructors
did their work. But in v6 onwards, that is no longer the case. TThread correctly
waits until all constructors have finished their work before then allowing the
thread to run."

This means that in the BCB4 version of RTC, I must be careful about constructing
objects in the TThread's constructor. Construction of objects could probably
be moved to the beginning of the Execute() function.

*/
//---------------------------------------------------------------------------
/*
https://stackoverflow.com/questions/71032513/deleting-dynamically-created-component

From Remy Lebeau   8 Feb 2022

You should NOT use the Form's OnCreate and OnDestroy events in C++Builder. They
are a Delphi idiom that can lead to undefined behavior in C++ if you are not
careful. OnCreate can fire before your Form's constructor, and OnDestroy can
fire after your Form's destructor. As such, you should use the Form's actual
constructor and destructor instead.

*/
//---------------------------------------------------------------------------
// Writing C++ Code for BCB 4.0, BCB 6.0 and CB10.1
// Requires option "Use Classic Borland Compiler to set to true in CB10.1
// http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Predefined_Macros#C.2B.2B_Compiler_Versions_in_Predefined_Macros
#if __BORLANDC__ >= 0x560
// Defined for BCB 6.0 C++ programs that use the VCL.
#endif
#if __BORLANDC__ >= 0x720
// Defined for CB10.1 C++ programs that use the VCL.
#endif
//---------------------------------------------------------------------------
/*

Use the DLL versions of LUA, SOCKET and MIME. Do not use the LIB versions.

That way only one copy of the LUA code has to be loaded.

--> luadll.lib has to be included in the BCB 4.0 and 6.0 projects
--> lua535dll.lib has to be included in the CB10.1 and CB10.3 projects

--> luadll.dll has to be present with the RTC executable for BCB 4.0 and 6.0 projects
--> lua535dll.dll has to be present with the RTC executable for CB10.1 and CB10.3 project

--> BCB4.0 version of mimecore.dll renamed to core.dll has to be in the socket folder for BCB 4.0 and 6.0
--> BCB4.0 version socketcore.dll renamed to core.dll has to be in the socket folder for BCB 4.0and BCB 6.0

--> CB10.1 version of mimecore.dll renamed to core.dll has to be in the socket folder for CB 10.1 and CB 10.3
--> CB10.1 version socketcore.dll renamed to core.dll has to be in the socket folder for CB 10.1 and CB 10.3

--> BCB 4.0 version of ssl.dll has to be present with the RTC executable for BCB 4.0 and 6.0 projects
--> CB 10.1 version of ssl.dll has to be present with the RTC executable for CB10.1 and CB10.3 projects

--> luaSec renamed to ssl.dll version 0.4.1 has to be present with the RTC executable for BCB 4.0 and 6.0 projects
--> luaSec renamed to ssl.dll version 0.8 has to be present with the RTC executable for CB10.1 and CB10.3 projects

--> for all compilers libeay32.dll and ssleay32.dll version 1.0.2s (Win 32 version) in the folder with the RTC executable.
*/
//---------------------------------------------------------------------------

#include <vcl.h>
#include <tchar.h>
#pragma hdrstop

// RTC Near Field Communications
#include "RTCMegaMaster.h"
#include "RTC_NFC.h"
#if RTC_NFC_VERSION < 4
#error Error - wrong version of RTC_NFC.h - need v4
#endif
#include "Engine_Commands.h"
#include "Train_Control.h"
#include "AIU_Control.h"
#include "Op_Control.h"
#include "Volume_Control.h"
#include "Tserial_event.h"
#include "Utility.h"
#include "Setup_Control.h"
#include "Debug_Controls.h"
#include "Num_Query.h"
#include "Message_Popup.h"
#include "Conv_Mode.h"
#include "DCS_Setup_Controls.h"
#include "LashUp_Selection.h"
#include "RTC_Info.h"
#include "TMCC_Control.h"
#include "Quick_Controls.h"
#include "RTC_Help.h"
#include "Route_Scene.h"
#include "Hot_Com.h"
#include "Super_TIU_Mode.h"
#include "Comm_Thread.h"
#include "Track_Signal_Thread.h"
#include "Track_Signal.h"
#include "Engine_Information_Thread.h"
#include "Engine_Info.h"
#include "Engine_Status_Thread.h"
#include "Engine_Status.h"
#include "Timer_Thread.h"
#include "PC_Thread.h"
#include "Prog_Control.h"
#include "Message_Thread.h"
#include "DL_Thread.h"
#include "DetectedTags.h"
#include "Layout.h"
#include "Clipbrd.hpp"
#include "EngineListPopOut.h"
#include "Super_TIU_Mode.h"
#include "TIUSetup.h"
#include "WatchdogSignal.h"
#include "Watchdog_Thread.h"
#include "Route_Scene_Thread.h"

#include <algorithm>	// STL - needed for lower_bound()
#include <dir.h>        // needed for getcwd() in CB10.1
#include <math.h>		// needed for pow() in BCB4.0
#include <stdio.h>
#include <stdlib.h>     // for malloc() and free()
#include <string.h>
#include <time.h>		// for printout timestamp
#include <mmsystem.h>	// for PlaySound

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

// Differences between BCB4.0/BCB6.0 and CB10.1
//
//

#if __BORLANDC__ >= 0x720
// used by TSocketServer's SSL routines
// These should have been linked in automatically but I couldn't get that to work
#pragma link "cryptui.lib"
#pragma link "crypt32.lib"
#pragma link "ncrypt.lib"
#endif

#pragma resource "*.dfm"
TMainForm *MainForm;

//---------------------------------------------------------------------------
// Copyright comment into the executable file
#pragma comment(exestr, "© Copyright 2024 by Mark DiVecchio")
//---------------------------------------------------------------------------

// pointers to Radio and RFID serial ports (actual thread created with "new")
Tserial_event * RFID_serial_port = NULL;
Tserial_event * Serial_port = NULL;

bool Ignore_FF_Char;

// Serial port settings
const static int Baud[] = {2400, 4800, 9600, 19200, 38400, 57600, 115200};
const static int Parity[] = {SERIAL_PARITY_NONE,  SERIAL_PARITY_ODD, SERIAL_PARITY_EVEN};
const static char Bits[] = {7, 8};
const static int Stop[] = {SERIAL_STOP_1, SERIAL_STOP_2};

// global vars that are used in a thread should be defined as volatile
volatile bool bStop;
volatile int CTS_Count, DSR_Count, CD_Count;
volatile bool bRFIDSendComplete;
//--------------------------------------------------------------------------------------
// RFID Serial Port Status
volatile bool bRFIDCOMConnected = false;	    // RFID Serial Port Connection Complete Flag
//volatile int iRFIDbufCount;					// RFID Serial Port Byte Counter
// Radio Serial Port Status
volatile bool bRadioCOMConnected = false;	    // Radio Serial Port Connection Complete Flag
// RFID UDP WiFi Port Status
volatile bool bRFIDUDPWiFiConnected = false;	// RFID UDP WiFi Port Connection Complete Flag
volatile int iRFIDUDPWiFibufCount = 0;			// RFID UDP WiFi Port Byte Counter
volatile bool bRFIDUDPWiFiRXConnected = false;	// RFID UDP WiFi Listen() Success Flag
// RFID TCP WiFi Port Status
volatile bool bRFIDTCPWiFiConnected = false;	// RFID TCP WiFi Port Connection Complete Flag
volatile int iRFIDTCPWiFibufCount = 0;			// RFID TCP WiFi Port Byte Counter
// AIU WiFi Port Status
volatile bool bAIUWiFiTXConnected;			// AIU WiFi TX Port Connection Complete Flag
// WTIU WiFi Port Status
volatile bool bWTIUWiFiConnected;	    // WTIU WiFi Port Connection Complete Flag
volatile int iWTIUWiFibufCount;		    // WTIU WiFi Port Byte Counter
//volatile bool bWTIUWiFiConnected[TIUMAX] = {false, false, false, false, false};	// WTIU WiFi Port Connection Complete Flag
//volatile int iWTIUWiFibufCount[TIUMAX] = {0, 0, 0, 0, 0};		    // WTIU WiFi Port Byte Counter
//--------------------------------------------------------------------------------------
// TIU Connection Status
volatile bool bTIUConnected = false;				// set to true when all TIU acknowledge a Get AIU Count 'x' Command
//--------------------------------------------------------------------------------------
// For threaded TIU Communications
volatile bool DisconnectRequest = false;		// Indicates [Disconnect] button was pressed
extern volatile bool RadioDataSent;				// Indicates that the Radio packet was sent either via COM or WiFi

bool bEmergencyStop;		// emergency stop in effect
bool RequestyCommand;       // When true during WIU_WiFi_Mode, generate a 'y' command

// global vars that might be used in an ISR should be tagged volatile
extern volatile byte cTIUbuf[CMDMAX];			// data from the TIU on Port A

// in C++ the array is zero based so the [0] entry is invalid, in Lua the array is one based
String AspectNames[] = {"ZERO", "None", "Dark", "Stop Signal", "Clear", "Stop and Proceed", "Approach", "Medium Clear",
	"Medium Approach", "Slow Clear", "Slow Approach", "Approach Medium", "Approach Slow",
	"Restricting", "Approach Limited", "Limited Clear", "Limited Approach", "Medium-Approach Slow", "Test"};

// Forward Declarations
unsigned char ValidDigit(unsigned char d);
unsigned char ValidByte(unsigned char c);
byte charToHex(byte ch);
unsigned char Hex2Digit_to_Decimal(unsigned char * str);
char * Amp(const char *a);
unsigned int HextoInt(char c);
//---------------------------------------------------------------------------
// Workaround for SetLayeredWindowAttributes() to make transparent windows
lpfnSetLayeredWindowAttributes m_pSetLayeredWindowAttributes;
//---------------------------------------------------------------------------
// Global variables used for v2 routines that keep the data
// as binary rather than in String data types (can't be static - referenced in Comm_Thread.cpp)
//byte cRFIDbuf[CMDMAX];	// data from the RFID Tag Readers on RFID Port
//
byte Sequence;			// Sequence number for TX packets
//---------------------------------------------------------------------------
// Pointers to Forms for 121 Engines/LashUps, 5 TIU each with 5 AIU:
//		Factory Reset Engine 0 (DCS 1)	 	(1)
//		Real Engines 1-99 (DCS 2-100)    	(99)
//		All Engine Operation 100 (DCS 101)	(1)
//		LashUps 101-120 (always DCS #102)	(20)  -- total of 121 entries indexed as 0-120
// initialized to NULL in the MainForm Constructor

TVolume * ptrVolume[ABSOLUTEMAX];	// Volume Control Pointers - indexed by actual engine number because 0 is a factory reset engine
TOp * ptrOp[ABSOLUTEMAX];   		// Operation Control Pointers - indexed by actual engine number because 0 is a factory reset engine
char * ptrLUEngineList[ABSOLUTEMAX];	// LashUp Engine List Pointers - indexed by actual lashup number because 0 is a factory reset engine
char * ptrLUNameList[ABSOLUTEMAX];		// LashUp Engine Name Pointers - indexed by actual lashup number because 0 is a factory reset engine
TAIUControl * ptrAIUControl[TIUMAX][AIUMAX];	// AIU Control Pointers for 25 AIU - indexed by TIUNo-1 and AIUNo-1
//struct AIU_EMU AIUEmulator[TIUMAX];				// Flags to indicate an AIU Emulator is attached to an emulated TIU
//struct MSG_DISPLAY MessageDisplay[100];         // Flags to indicate a message display is available
struct SIGNAL Signals[SIGNALMAX];				// Flags to indicate a Signal was found
struct DETECTOR Detector[RFIDMAX];              // Flags to indicate an RFID detector device with up to 2 readers was found
int AIUCount[TIUMAX] = { 0, 0, 0, 0, 0	// Number of AIU connected to each TIU
#if TIUMAX == 8
	, 0, 0, 0
#endif
	};
TConvMode * ptrConvMode[TIUMAX][CONVMAX];	   // Conventional Mode Pointers, 5 TIU, 2 channels - indexed by TIUNo-1 and ChannelNo-1
TDCS_Setup * ptrDCS[TIUMAX];			// DCS Setup Pointers, 5 TIU - indexed by TIUNo-1
TQuickCtrl * ptrQuick[QUICKPAGEMAX];   	// Quick Control Pointers
TWatchdog * ptrWatch[TIUMAX];          	// Watchdog Signal Pointers
float ScalingFactor[ENGINEMAX];	        // cached DTO and DOD Scaling Factors read up from engines 0-99
byte PSType[ENGINEMAX];		            // cached engine type PS2 or PS3 from engines 0-99
// TODO : TMCC code needs to be completed
// #define TMCCENGINEMAX 100
//TTMCCOp * ptrTMCCOp[TIUMAX][TMCCENGINEMAX];	// TMCC Operation Control Pointers - indexed by actual engine number, 1-99 index 0 is not used
THot_Command * ptrHot_Command[HOTPAGEMAX];		// Hot Button Pointers, up to 5 windows for 100 buttons
TEngineInfo * ptrEngineInfo[ABSOLUTEMAX];		// Engine Information Pointers - indexed by actual engine number because 0 is a factory reset engine
TEngineStatus * ptrEngineStatus[ABSOLUTEMAX];	// Engine Status Pointers - indexed by actual engine number because 0 is a factory reset engine
TTrackSignal * ptrTrackSignal[ABSOLUTEMAX];	// Track Signal Pointers - indexed by actual engine number because 0 is a factory reset engine
TCommThread * ptrTIUCommThread;				// pointer to TIU communications thread
TDispatch_List_Thread * ptrDL_Thread;		// pointer to Dispatch List Thread
TThread * PBThread;							// pointer to Setup's playback thread
TThread * ptrPBThread[20 * HOTPAGEMAX];		// pointers to Hot Com's playback threads
TProgram_Control *ptrProgram_Control[PCMAX];	// pointer(s) to Program Control Windows
TThreadList * Event_FIFO[PCMAX];	    	//  FIFO Event List for Program Control Threads - Thread Safe
TAbNumEdit *ptrPC_ETime[PCMAX];				// pointer(s) to ETime control in each Program Control window
//
int TimeStampMode;                          // Debug messages time stamping
int PC_Tenths;								// RunTimeinTenths counter (system wide)
int ptrPC_Tenths[PCMAX];					// RunTimeinTenths counter in each Program Control window
TMyTimerThread * ptrTimer_Thread;			// pointer to single Timer Thread
volatile HANDLE hRFIDReqEvent[PCMAX];		// pointer(s) to RFID Events created by PC threads
TWSocket * WTIUTXRX[TIUMAX] = {NULL, NULL, NULL, NULL, NULL
#if TIUMAX == 8
	, NULL, NULL, NULL
#endif
	};       								// pointers to TCP Sockets for each WTIU
volatile bool WTIUSessionsConnected;        // set to true on all TCP connection to the WTIUs successful
volatile bool WTIUSessionsAttemptComplete;  // set to true on all WTIU connection attempts complete
volatile int SelectedWTIUPortCount = 0;		// Count of TIU connected
//
bool TIUSelection[TIUMAX];                      // Flags to indicate that a TIU is present
int TIUVersion[TIUMAX] = {0, 0, 0, 0, 0};       // TIU version number as an int * 100 (eg v6.10 will have a value of 610)
String TIUVersionString[TIUMAX];                // TIU Version number as a string
bool SuperTIUMode, TIUSuperMode[TIUMAX];        // Flags to indicate if Super TIU Mode is active

// J13HexString for All Engine Operation must be AnsiString because the c_str() taken needs to be a char type
AnsiString J13HexString;            // All Engine Operation
//---------------------------------------------------------------------------
// Signal number of each of two cab signals
int iCabSignal[PCMAX][2];					// Signal number of each of two cab signals in PC Control window
											// [0] is HOMESIGNAL, [1] is DISTANTSIGNAL
//---------------------------------------------------------------------------
// ComboBox filename for saved items
String TagLocationsFileName = "TagLocations.txt";
String EngineTypesFileName = "EngineTypes.txt";
String CarModelsFileName = "CarModels.txt";
String EngineModelsFileName = "EngineModels.txt";
String RailroadNamesFileName = "RailroadNames.txt";

//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//--------------------- MainForm  Constructor -------------------------------
//---------------------------------------------------------------------------
__fastcall TMainForm::TMainForm(TComponent* Owner)
	: TForm(Owner)
{
//MessagePopup = NULL;	// make sure the pointer is NULL
iSS = 1;		        // Splash Window if == 1
// Required for multiple monitors and save/restore position/size
Position=poDesigned;
DefaultMonitor=dmDesktop;
//
// shrink MainForm
// First save the larger debug window size
SavedDebugWidth = Width;
SavedDebugHeight = Height;
// Calculate the smaller normal operation window size
//iSavedWidth = ESTOP->Left + ESTOP->Width + 40; // 40 + 516 + 40 = 596;
iSavedWidth = ESTOP->Left + ESTOP->Width + 140; // 140 + 316 + 140 = 596;
iSavedHeight = ESTOP->Top + ESTOP->Height + StatusBar->Height + 42; // 327;
// Set the window to the smaller size
Width = iSavedWidth;
Height = iSavedHeight;
// Status Bar widths
StatusBar->Panels->Items[TIU_PORT_STATUS]->Width = Width / 3;
StatusBar->Panels->Items[TIU_STATUS]->Width = Width / 3;
// make sure Debug flag is cleared
bDebug = false;
// Insert compile date and time into *.exe for information
String MyDT = __DATE__ ;
MyDT += " - ";
MyDT += __TIME__;
//
#if __BORLANDC__ >= 0x740
Date_Time->Caption = MyDT + " (CB 10.3 0x" + IntToHex(__TCPLUSPLUS__, 4) + ")";	// CB10.3 Rio
#elif __BORLANDC__ >= 0x720
Date_Time->Caption = MyDT + " (CB 10.1 0x" + IntToHex(__TCPLUSPLUS__, 4) + ")";	// CB10.1 Berlin
#elif __BORLANDC__ >= 0x560
Date_Time->Caption = MyDT + " (BCB 6.0 0x" + IntToHex(__TCPLUSPLUS__, 4) + ")";	// BCB 6.0
#else
Date_Time->Caption = MyDT + " (BCB 4.0 0x" + IntToHex(__TCPLUSPLUS__, 4) + ")";	// BCB 4.0
#endif

Application->ShowHint = true;			// global hint enable but does not work for menus
LocalTransparencyOverride = false;
MsgNum = 0;								// initialize message number counter

#if __BORLANDC__ >= 0x720 && ENABLENOTIFICATIONS
RTCNotificationCenter = new TNotificationCenter(NULL);
#endif

// This has no effect in BCB4.0 and Thread view not available in CB10.1 Community Edition
// It does work on BCB6.0 and shows the thread name in the Thread View window
// http://docwiki.embarcadero.com/RADStudio/Rio/en/Converting_an_Unnamed_Thread_to_a_Named_Thread
THREADNAME_INFO info;
info.dwType = 0x1000;
info.szName = "VCL_Thread";
info.dwThreadID = -1;
info.dwFlags = 0;
__try {
	RaiseException(0x406D1388, 0, sizeof(info) / sizeof(DWORD), (DWORD*)&info);
	}
__except (EXCEPTION_CONTINUE_EXECUTION) {
	}

// NameThreadForDebugging is not available in BCB4.0 or BCB6.0
// TThread::NameThreadForDebugging("Message_Thread");
// http://docwiki.embarcadero.com/Libraries/Rio/en/System.Classes.TThread.NameThreadForDebugging

#ifdef DEBUGTESTCODE
// https://docs.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-isdebuggerpresent
if (IsDebuggerPresent()) {
	// https://docs.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-outputdebugstringa
    OutputDebugString(TEXT("Break in MainForm constructor"));
	// https://docs.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-debugbreak
    DebugBreak();
    }
#endif

//
// generate the version number and put it in the Caption, Status Bar & popup menu
//
DWORD  verHandle = NULL;
UINT   size      = 0;
LPBYTE lpBuffer  = NULL;
DWORD  verSize   = GetFileVersionInfoSize( Application->ExeName.c_str(), &verHandle);

if (verSize != NULL) {
    LPSTR verData = new char[verSize];	// deleted below
    assert (verData != NULL);
	if (GetFileVersionInfo( Application->ExeName.c_str(), verHandle, verSize, verData)) {
        if (VerQueryValue(verData,TEXT("\\"),(VOID FAR* FAR*)&lpBuffer,&size)) {
            if (size) {
                VS_FIXEDFILEINFO *verInfo = (VS_FIXEDFILEINFO *)lpBuffer;
                if (verInfo->dwSignature == 0xfeef04bd) {
                    // Doesn't matter if you are on 32 bit or 64 bit,
                    // DWORD is always 32 bits, so first two revision numbers
                    // come from dwFileVersionMS, last two come from dwFileVersionLS
					_sntprintf(Version, sizeof Version, TEXT("V%d.%d.%d.%d (%s)"),
                    ( verInfo->dwFileVersionMS >> 16 ) & 0xffff,
                    ( verInfo->dwFileVersionMS >>  0 ) & 0xffff,
                    ( verInfo->dwFileVersionLS >> 16 ) & 0xffff,
                    ( verInfo->dwFileVersionLS >>  0 ) & 0xffff,
                    TEXT(__DATE__));
                    StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = Version;
                    AboutRTC->Caption = "About RTC " + StatusBar->Panels->Items[TIU_PORT_STATUS]->Text;
                    Caption = Caption + " - " + StatusBar->Panels->Items[TIU_PORT_STATUS]->Text;
                    SavedCaption = Caption;
                	}
            	}
        	}
    	}
    delete[] verData;	// this is the only place where verData is deleted
	} else {
    // Retrieve the system error message for the last-error code
    LPVOID lpMsgBuf;
    DWORD dw = GetLastError();

    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | 
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &lpMsgBuf,
        0, NULL );
    // Display the error message
    MessageBox(NULL, (LPCTSTR)lpMsgBuf, TEXT("Error"), MB_OK);
	LocalFree(lpMsgBuf);
    }

_tgetcwd(cStartDir, MAX_PATH);	// Directory for *.ini and Car/Engine/Railroad files
Last_Response->Text = cStartDir;	// cStartDir never changes

WorkingDir =  cStartDir;		// Directory for save/load file diaglogs
								// WorkingDir changes depending on the user changing folders in the dialog boxes
                                // WorkingDir is saved in the registry on exit
CTS_Count = 0;		// TIU Port Signal counters
DSR_Count = 0;
CD_Count = 0;
LastHotComPage = 1;
RequestyCommand = false;
// User Registry Data
RegFileRootKey = "Software\\Remote Control5\\Serial";   // also edit RegButtonClick()
// Counters
CTS_Counter->Text = String(CTS_Count);
DSR_Counter->Text = String(DSR_Count);
CD_Counter->Text = String(CD_Count);
// Buttons
DisconnectButton->Enabled = false;
ConnectButton->Enabled = true;
ConnectButton->Caption = "&Connect";
ExitButton->Enabled = true;
Send_ASCII_TIU->Enabled = false;
Send_ASCII_RFID->Enabled = false;
Send_Hex_TIU->Enabled = false;
Send_Hex_RFID->Enabled = false;

// All controls disabled on start
SoundCtrlsButton->Enabled = false;
OpButton->Enabled = false;
StartupButton->Enabled = false;
QuickButton->Enabled = false;
Var1ModeButton->Enabled = false;
Var2ModeButton->Enabled = false;
ShutdownButton->Enabled = false;
StopAllButton->Enabled = false;
ReadEnginesButton->Enabled = false;

// AIU Button enabled at start
AIUButton->Enabled = true;

// PC Button enabled on start
Prog_Control_Button->Enabled = true;

// DTR and RTS disabled on start
RTSSwitch->Enabled = false;
DTRSwitch->Enabled = false;

STOPem->Enabled = false;
StartedConsists->Value = 0;
CommandDecoded = false;

AltKeyState = ShiftKeyState = CtrlKeyState = 0;

// set max TIU and AIU Spinners
TIUSpin->MaxValue = TIUMAX;
AIUSpin->MaxValue = AIUMAX;
RemoteSpin->MaxValue = REMOTEMAX;

// check for command line parameters <TIUNo> <RemoteNo>
ProgName->Caption = ParamStr(0);
int PCount = ParamCount();
RemoteNo = -1;
DebugCommandLine = false;
try {
	if (PCount > 0) for (int i = 1 ; i <= PCount ; i++) {
        if (i == 2 && ParamStr(i)[1] != '/') {
        	RemoteNo = ParamStr(2).ToIntDef(1);		// Remote Number
            if (RemoteNo < 0 || RemoteNo > REMOTEMAX) throw(11);	// Remote 0 to 15
            RemoteSpin->Value = RemoteNo;
            continue;
            }
        // See if it's a parameter starting with /
        if (ParamStr(i)[1] == '/') {
        	// "/d" Debug
            if (ParamStr(i)[2] == 'd') {
            	DebugCommandLine = true;
                }
            //
            // "/iZZZZZ" Identity used to access Registry for Program settings
            if (ParamStr(i)[2] == 'i') {
            	RegFileRootKey = RegFileRootKey + "-" + ParamStr(i).SubString(3,10);
                }
			//
            continue;
            }
		}
    }
catch(int) {
    ShowMessage("Invalid Command Line Parameter(s)" + String("\n") + "Should be two numbers - TIU # (1 to 5) and Remote # (0 to 15)");
    Application->Terminate();
	}
catch(...) {
    ShowMessage("Invalid Command Line Parameter(s)" + String("\n") + "Should be TIU # and Remote # separated by a space");
    Application->Terminate();
	}

// read Preferences from registry if they exist
TRegIniFile * const RegFile = new TRegIniFile(RegFileRootKey);
assert(RegFile != NULL);			// make sure that construction worked
// display for debug
Last_Message->Text = RegFileRootKey;
__try {
	TIUSpin->ValueAsInt = RegFile->ReadInteger("Options", "TIU", 1);	// causes TIUNo to change (does not cause OnChange event to fire)
    TIUSpinChange(NULL);   // causes OnChange event
    TIUSpin->MinValue = 1;
    assert(TIUSpin->ValueAsInt >= 1 && TIUSpin->ValueAsInt <= TIUMAX);
    //
    if (RemoteNo < 0) { 	// not given on command line so read it from the Registry
	    RemoteSpin->Value = RegFile->ReadInteger("Options", "Remote", REMOTEMAX);
	    RemoteNo = RemoteSpin->Value;
        assert(RemoteNo >= 0 && RemoteNo <= REMOTEMAX);
        }
	//
    // Engine No and AIUNo loaded by LoadEngineData()
    //
    // Read new startup window position
	NewTop = RegFile->ReadInteger("Options", "Top", Top);
	NewLeft = RegFile->ReadInteger("Options", "Left", Left);
    // window size is automatic and not saved
    // Options common over all TIU
	PlainTextFile->Checked = RegFile->ReadInteger("Options", "PlainTextFile", PlainTextFile->Checked);
	TIULog->Checked = RegFile->ReadInteger("Options", "TIULog", TIULog->Checked);
	RFIDLog->Checked = RegFile->ReadInteger("Options", "RFIDLog", RFIDLog->Checked);
	CRCSel->ItemIndex = RegFile->ReadInteger("Options", "CRCSel", CRCSel->ItemIndex);
    WorkingDir = RegFile->ReadString("WiFi","WorkingDir" + String(IntToHex(__TCPLUSPLUS__, 4)), WorkingDir);
    iSS = RegFile->ReadInteger("Options", "SS", iSS);	// this gets set to 0 when you click on RTCInfo Button
    if (iSS == 0) AboutRTC->Checked = true;
	// Engine RAM Dump
    LastLine->ValueAsInt = RegFile->ReadInteger("Debug", "LastLine", LastLine->ValueAsInt);
    FirstLine->ValueAsInt = RegFile->ReadInteger("Debug", "FirstLine", FirstLine->ValueAsInt);
    LastHotComPage = RegFile->ReadInteger("Options", "LastHotComPage", LastHotComPage);
    }
__finally {
	// Deallocate RegFile
	delete RegFile;
    }
//
if (iSS) {
	// Splash Window
    Splash = new TSplash(Application);
    Splash->Memo1->Lines->Strings[0] = "Remote Train Control - " + String(Version);
    Splash->Show();			// Show Splash Window
    Splash->Update();		// Paint Splash Window
    }

// Load strings into Combo Boxes if file exists
if (FileExists(String(cStartDir) + "\\" + CarModelsFileName)) {
	CarModelComboBox->Items->LoadFromFile(String(cStartDir) + "\\" + CarModelsFileName);
	CarModelComboBox->ItemIndex = 0;
	}
if (FileExists(String(cStartDir) + "\\" + EngineModelsFileName)) {
	EngineModelComboBox->Items->LoadFromFile(String(cStartDir) + "\\" + EngineModelsFileName);
	EngineModelComboBox->ItemIndex = 0;
	}
if (FileExists(String(cStartDir) + "\\" + RailroadNamesFileName)) {
	RailroadNameComboBox->Items->LoadFromFile(String(cStartDir) + "\\" + RailroadNamesFileName);
	RailroadNameComboBox->ItemIndex = 0;
	}
if (FileExists(String(cStartDir) + "\\" + TagLocationsFileName)) {
	TagLocationComboBox->Items->LoadFromFile(String(cStartDir) + "\\" + TagLocationsFileName);
	TagLocationComboBox->ItemIndex = 0;
	}
if (FileExists(String(cStartDir) + "\\" + EngineTypesFileName)) {
	EngineTypeComboBox->Items->LoadFromFile(String(cStartDir) + "\\" + EngineTypesFileName);
	EngineTypeComboBox->ItemIndex = 0;
	}

ConnectMsg = "Press the [Connect] button before performing this operation";
DisconnectMsg = "Press the [Disconnect] button before performing this operation";
//WTIUMsg = "This feature is not yet implemented for the WTIU";
WTIUOnlyMsg = "This feature only works with the WTIU";
//WiFiOnlyMsg = "This feature only works with the WIU/TIU or the WTIU";
TXMsg = "Two Way Communications is required before performing this operation";
TIUActiveMsg = "The selected WTIU/TIU must be active and connected before performing this operation";
TIUNoLashupMsg = "Before you can use this Lashup, the head engine must be located by the [Read] button";
TIUNoEngineMsg = "Before you can use this Engine, the engine must be located by the [Read] button";
//TIUVerMsg = "RTC requires TIU with DCS Code version 6.00 or newer";
TIUSelMsg = "No Active TIU(s) selected, use [Setup]->[Select Active TIU] before performing this operation";
RFIDWiFiMsg = "RFID WiFi must be enabled before performing this operation";
//
bRecording = bPlayingBack = bPlaybackCommError = false;
iPlayingBackCounter = 0;		// indicate no playback is taking place
bEmergencyStop = false;         // indicate no Emergency Stop in effect


// clear pointer arrays and data
bEngineDataLoaded = false;
bEngineDataCorrupt = false;

for (int iEng = 0; iEng < ABSOLUTEMAX ; iEng++) {  	// Engine 0 to 120 - factory reset to end of LashUps
    ptrVolume[iEng] = NULL;
    ptrOp[iEng] = NULL;
    ptrLUEngineList[iEng] = NULL;
    ptrLUNameList[iEng] = NULL;
    ptrEngineInfo[iEng] = NULL;
    ptrTrackSignal[iEng] = NULL;
    ptrEngineStatus[iEng] = NULL;
    }
// Initialize the TIU/WTIU variables
for (int iTIU = 1; iTIU <= TIUMAX ; iTIU++) { // TIU 1-5
    AIUCount[iTIU-1] = 0;
    //AIUEmulator[iTIU-1].Found = false;
    TIUVersion[iTIU-1] = 0;
    TIUVersionString[iTIU-1] = "&Connect";
    ptrWatch[iTIU-1] = NULL;
    }
// Initialize the Signals flags
for (int iSig = 0; iSig < SIGNALMAX ; iSig++) {
    Signals[iSig].Found = false;
    }

for (int iPage = 1; iPage <= QUICKPAGEMAX ; iPage++) { // Quick Controls
    ptrQuick[iPage - 1] = NULL;
    }

// clear cached Scaling Factor array and Engine Type array.
// if another PC window has already loaded these items, after
// we clear them, they will just get loaded again the next time tht
// a RAM read is performed.
for (int iEng = FACTORYRESET; iEng <= ENGINEMAX ; iEng++) {  	// Engine 0 to 99
	ScalingFactor[iEng] = 0.0;
	PSType[iEng] = UDEF0;
	}

// clear pointer arrays for each TIU/AIU pair & each Conventional channel, DCS and Lashup
for (int iTIU = 1; iTIU <= TIUMAX ; iTIU++) { // TIU 1-5
    for (int iConv = 1 ; iConv <= CONVMAX ; iConv++) {  // Conventional Chan 1-2
    	ptrConvMode[iTIU-1][iConv-1] = NULL;	// Var Channel
        }
    ptrDCS[iTIU-1] = NULL;
   	for (int iAIU = 1 ; iAIU <= AIUMAX ; iAIU++) {   // AIU 1-5
       	ptrAIUControl[iTIU-1][iAIU-1] = NULL;
        }
    }
// clear pointer arrays for Program Control windows
for (int iPCNo = 1; iPCNo <= PCMAX ; iPCNo++) { // windows 1-5
    ptrProgram_Control[iPCNo - 1] = NULL;
    ptrPC_ETime[iPCNo - 1] = NULL;
    ptrPC_Tenths[iPCNo - 1] = 0;	// clear the Timer Thread's local elapsed times
    hRFIDReqEvent[iPCNo - 1] = NULL;
    Event_FIFO[iPCNo - 1] = NULL;
    iCabSignal[iPCNo - 1][HOMESIGNAL] = iCabSignal[iPCNo - 1][DISTANTSIGNAL] = 0;	// Home and Distant Cab signal numbers
	}

PC_Tenths = 0;	// clear the Timer Thread's global time

for (int iHot = 1; iHot <= HOTPAGEMAX ; iHot++) { // Hot Button pages
    ptrHot_Command[iHot-1] = NULL;
	}

ptrTIUCommThread = NULL;
ptrDL_Thread = NULL;

// create the Setup window
// This code should work with auto create on OR off.
// ------------ With programmed create using C++ Builder's generated pointer
// Setup Form should be manually created
if (!Setup) {	// since I used the C++ pointer, it will get automatically deleted at close
	Setup = new TSetup(this, 1, RemoteNo);     // Pass "this" as the owner
	assert(Setup != NULL);			// make sure that construction worked
    //Setup->COMSelGroup->Top = 16;	// Serial port box position for non-debug mode
	//Setup->SerialPortListGroup->Top = 16;
   	}
if (Setup->DisableEmergencyStopButton->Checked) {
	ESTOP->Visible = false;
    ESTOP->Enabled = false;
    ESTOP->Hint = "Disabled in Setup";
	ESTOP->Cursor = crNo;
    }
//---------------------------------------------------------------------------
//
// Load the configuration script
//
LoadConfig();

//
// Do the WiFi Scans
//

if (Setup->RFID_WiFi_Mode->Checked) {
    // enable all items
    MainForm->RFIDBuffer->Enabled = true;
    MainForm->RFIDBuffer->Color = clWindow;
    Setup->HostIPPage->Enabled = true;
    // Find all RFID devices
    Setup->RFIDListBox->Enabled = true;
    Setup->RFIDWiFiLabel->Enabled = true;
    Setup->ScanRFIDButtonClick(NULL);
    }


if (Setup->Signals_WiFi_Mode->Checked) {
    // Find all Signal devices
    Setup->SignalsWiFiLabel->Enabled = true;
    Setup->SignalsListBox->Enabled = true;
    Setup->HostIPPage->Enabled = true;
    Setup->ScanSignalsButtonClick(NULL);
	}

if (Setup->WTIU_WiFi_Mode->Checked) {
    // Find all WTIU devices
    Setup->WTIUListBox->Clear();
    Setup->ScanTIUWIUtcpClick(NULL);
    Setup->WTIUListBoxClick(NULL);
    }
//---------------------------------------------------------------------------
bSaveRegistryInfo = true;	// Normally we save Registry info in the Registry - except
							// when user clicked Reset Preferences button in Debug Mode

// Fill the Engine Data specific data from a disk file
LoadEngineData(EngineData);	// First Time load of EngineData from disk EngineData.ini file
                            // sets bTIUDataLoaded to true
EngineData->DefaultColWidth = 30;		// default
EngineData->ColWidths[ENG_NAME] = EngineData->Width - EngineData->DefaultColWidth;	// lots of space for the engine name
ActEngLst->ColWidths[ENG_NAME] = EngineData->Width - EngineData->DefaultColWidth;	// CKR

// in RFID Com mode, enable the serial port select
Setup->RFIDCOMPortNum->Enabled = Setup->RFID_COM_Mode->Checked;
Setup->RFIDCOMLabel->Enabled = Setup->RFID_COM_Mode->Checked;
// in Radio Com mode, enable the serial port select
Setup->RadioCOMPortNum->Enabled = Setup->Radio_COM_Mode->Checked;
Setup->RadioCOMLabel->Enabled = Setup->Radio_COM_Mode->Checked;
Setup->BaudRateSel->Enabled = Setup->Radio_COM_Mode->Checked;
Setup->ParitySel->Enabled = Setup->Radio_COM_Mode->Checked;
Setup->BitsSel->Enabled = Setup->Radio_COM_Mode->Checked;
Setup->StopSel->Enabled = Setup->Radio_COM_Mode->Checked;
// hints
NumDetectors->Visible = Setup->RFID_COM_Mode->Checked || Setup->RFID_WiFi_Mode->Checked;
NumReaders->Visible = NumDetectors->Visible;
NumSignals->Visible = Setup->Signals_WiFi_Mode->Checked;
DetLabel->Visible = NumDetectors->Visible;
RdrLabel->Visible = NumDetectors->Visible;
SigLabel->Visible = NumSignals->Visible;

// expand the size of all RichEdit controls
//https://stackoverflow.com/questions/6049053/increase-size-limit-of-trichedit
RFIDBuffer->MaxLength = System::MaxInt-2;
TIUBuffer->MaxLength = System::MaxInt-2;
CRCBuffer->MaxLength = System::MaxInt-2;
RecordingBuffer->MaxLength = System::MaxInt-2;

//
#if __BORLANDC__ < 0x720
// Workaround BCB4.0 for SetLayeredWindowAttributes() which enables transparent windows
/*Load dll that holds the API func*/
HMODULE hUser32 = GetModuleHandle("USER32.DLL");
/*Grab pointer to API*/
m_pSetLayeredWindowAttributes = (lpfnSetLayeredWindowAttributes)GetProcAddress(hUser32, "SetLayeredWindowAttributes");
if (NULL == m_pSetLayeredWindowAttributes) {
	MessageBox(NULL,"Error!","SetLayeredWindowAttributes Not Found",MB_OK);
	return;
	}
#endif
//
EngineData->ScrollBars = ssVertical;	// sometimes we lose this setting
ActEngLst->ScrollBars = ssVertical;
//
//
// Experimental - use Drop Down Menu style in CB10.1
#if __BORLANDC__ >= 0x720
// bsSplitButton defined in Vcl.StdCtrls.hpp is really not defined
enum DECLSPEC_DENUM TButtonStyle : unsigned char { bsPushButton, bsCommandLink, bsSplitButton };
Prog_Control_Button->Style = (TCustomButton::TButtonStyle)bsSplitButton;
Prog_Control_Button->DropDownMenu = PCPopupMenu;
AIUButton->Style = (TCustomButton::TButtonStyle)bsSplitButton;
AIUButton->DropDownMenu = TIUPopupMenu;
StartupButton->Style = (TCustomButton::TButtonStyle)bsSplitButton;
StartupButton->DropDownMenu = StartupPopupMenu;
HotCommandButton->Style = (TCustomButton::TButtonStyle)bsSplitButton;
HotCommandButton->DropDownMenu = HotButtonPopup;
ReadEnginesButton->Style = (TCustomButton::TButtonStyle)bsSplitButton;
ReadEnginesButton->DropDownMenu = ReadPopupMenu;
SetupButton->Style = (TCustomButton::TButtonStyle)bsSplitButton;
SetupButton->DropDownMenu = SetupPopupMenu;
#endif
// -- create Dispatch List request event
DispatchListReqEvent = CreateEvent(NULL, false, false, NULL);	// Auto Reset - nonsignaled initial state
assert(DispatchListReqEvent != NULL);
// -- Create terminate events
// -- create Terminate Comm Thread Event
TerminationCTEvent = CreateEvent(NULL, false, false, NULL);	// Auto Reset - nonsignaled initial state
assert(TerminationCTEvent != NULL);
// -- create Terminate Dispatch List Thread Event
TerminationDLEvent = CreateEvent(NULL, false, false, NULL);	// Auto Reset - nonsignaled initial state
assert(TerminationDLEvent != NULL);
// -- create Terminate Playback Thread Event - this event will terminate any (and all) Playback threads
TerminationPBEvent = CreateEvent(NULL, false, false, NULL);	// Auto Reset - nonsignaled initial state
assert(TerminationPBEvent != NULL);
//---------------------------------------------------------------------------
// Critical Section to protect Program Control window's Flags and Global integers & strings
InitializeCriticalSection(&Global_Flag_CS);
// Critical Section to protect the Fixed Signals
InitializeCriticalSection(&Signals_CS);
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//
//Create Program Control popup menu based on PCMAX
//
//PCPopupMenu
// remove any menu items
int PCCount = PCPopupMenu->Items->Count;
if (PCCount > 0) {
	for (int i = 0 ; i < PCCount; i++) {
    	//PCPopupMenu->Items->Delete(0);
        delete PCPopupMenu->Items->Items[0];
		}
    }
    
TMenuItem * PC_Popup = NULL;
// PCMAX count of menu items
// read MRU from registry if they exist
TRegIniFile * const RegFile1 = new TRegIniFile(RegFileRootKey + "\\Program_Control");
assert(RegFile1 != NULL);
__try {
    for (int PCNo = 1 ; PCNo <= PCMAX ; PCNo++) {
        PC_Popup = new TMenuItem(PCPopupMenu);  // creates PCMAX number of PC_Popup menu items
        PC_Popup->Name = "ProgramControl" + String(PCNo);
        int ItemIndex = RegFile1->ReadInteger("PC" + String(PCNo), "MRUItemIndex" + String(PCNo), -1);
        if (ItemIndex < 0) ItemIndex = 0;
        String S = RegFile1->ReadString("PC" + String(PCNo), "MRU" + String(PCNo) + String(ItemIndex + 1), "");
   		if (!S.IsEmpty()) {
            AnsiString MenuText = "PC#" + String(PCNo) + " - " + ExtractFileName(S);
            char * G = Amp(MenuText.c_str());
            PC_Popup->Caption = String(G);
            free (G);
            PC_Popup->Hint = S;
			} else {
            PC_Popup->Caption = "Program Control #" + String(PCNo);
            }
        PC_Popup->Tag = PCNo;
        PC_Popup->OnClick = ProgramControlClick;
        PCPopupMenu->Items->Add(PC_Popup);
        }
    }
__finally {
	// Deallocate RegFile
	delete RegFile1;
	}    

// Separator
PC_Popup = new TMenuItem(PCPopupMenu);  // creates another PC_Popup menu item
PC_Popup->Name = "Separator1";
PC_Popup->Caption = "-";
PCPopupMenu->Items->Add(PC_Popup);

// Two menu items "Detected Tags Window" and "Layout Window"
PC_Popup = new TMenuItem(PCPopupMenu);  // creates another PC_Popup menu item
PC_Popup->Name = "ShowDetectedTags";
PC_Popup->Caption = "Show Detected Tags Window";
PC_Popup->Enabled = true;
PC_Popup->Checked = false;
PC_Popup->Hint = "Show all of the detected tags in a window";
PC_Popup->OnClick = ShowDetectedTagsClick;
PC_Popup->ShortCut = ShortCut(Word('D'), TShiftState() << ssCtrl);
PCPopupMenu->Items->Add(PC_Popup);

PC_Popup = new TMenuItem(PCPopupMenu);  // creates another PC_Popup menu item
PC_Popup->Name = "ShowLayoutWindow";
PC_Popup->Caption = "Show Layout Window";
PC_Popup->Enabled = true;
PC_Popup->Checked = false;
PC_Popup->Hint = "Show the layout and occupancy flags in a window";
PC_Popup->OnClick = ShowLayoutWindowClick;
PC_Popup->ShortCut = ShortCut(Word('L'), TShiftState() << ssCtrl);
PCPopupMenu->Items->Add(PC_Popup);
//
// Tell MainForm that we updated the Menu
TNotifyEvent();

//----------------------------------
//
//Create AIU Control popup menu based on AIUMAX and TIUMAX
//
#if AIUMAX > 5
AIU6Control1->Visible = true;
#endif
#if AIUMAX > 6
AIU7Control1->Visible = true;
#endif
#if AIUMAX > 7
AIU8Control1->Visible = true;
#endif
#if AIUMAX > 8
AIU9Control1->Visible = true;
#endif
#if AIUMAX > 9
AIU10Control1->Visible = true;
#endif
#if TIUMAX > 5
TIU61->Visible = true;
#endif
#if TIUMAX > 6
TIU71->Visible = true;
#endif
#if TIUMAX > 7
TIU81->Visible = true;
#endif

//By default, modern versions of Windows hide underlined cues [&Connect] until you press
//the Alt key. You can force Windows to display them persistently by using this:
//
// SystemParametersInfo(SPI_SETKEYBOARDCUES, 1, (void *)TRUE, SPIF_SENDCHANGE);
//

#if __BORLANDC__ >= 0x720 && ENABLENOTIFICATIONS
ShowNotification("Ready");     // Message
#endif

randomize();	// initialize random number generator for creation of sequence number
Sequence = random(256);	// Global sequence number for transmissions - remote seems to always start at 175
						// this unsigned char rolls 255->0
}
//---------------------------------------------------------------------------

__fastcall TMainForm::~TMainForm(void)
{
//
// Close Event handles and critical section flags
//
CloseHandle(TerminationCTEvent);	// not really needed - the system closes the handle automatically when the process terminates
CloseHandle(TerminationDLEvent);	// not really needed - the system closes the handle automatically when the process terminates
									// TerminationDLEvent clears and frees memory for dispatch list items
CloseHandle(TerminationPBEvent);	// not really needed - the system closes the handle automatically when the process terminates
CloseHandle(DispatchListReqEvent);	// not really needed - the system closes the handle automatically when the process terminates

DeleteCriticalSection(&Global_Flag_CS);
DeleteCriticalSection(&Signals_CS);
/*
// Display ThreadId in debug buffer - NOTE Debug_Controls form is already destroyed
if (DEBUGGING) {
	DB->Debug_Print("TMainForm:~MainThreadID " + IntToHex((int)MainThreadID, 8), clGreen);
   	// MainThreadID and GetCurrentThreadId() return the same value
	//DB->Debug_Print("TMainForm:~ThreadID " + IntToHex((int)GetCurrentThreadId(), 8), clGreen);
    }
*/
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::FormShow(TObject *Sender)
{
assert(Setup); 	// Setup must be constructed
// if using multiple monitors and one was removed, pull the window
// back onto the remaining monitor
int MC = Screen->MonitorCount;
if (MC == 1) {
    if (NewLeft < 0 ) NewLeft = 0;
    int Wth = Screen->Monitors[0]->Width;
    if (NewLeft > Wth) NewLeft -= Wth;
    if (NewTop < 0 ) NewTop = 0;
    int Hth = Screen->Monitors[0]->Height;
    if (NewTop > Hth) NewTop -= Hth;
    }
// set the window position
Top = NewTop;
Left = NewLeft;
// This sets the debugging option after DB is constructed
//
// Configuration Script
//
/*
	-- Remote Train Control Configuration File

	-- Enable debugging screens
	Debugging = true	-- Enable
	DBLevel = 0			-- Debug level 0 to 10

*/
// Lua state M is created in the MainForm constructor
if (MainForm->M != NULL) {
	if (lua_getglobal(MainForm->M, "Debugging") == LUA_TBOOLEAN) {
		if (lua_toboolean(MainForm->M, -1)) DebugButtonClick(NULL);	// Show Debug window - true or false
		}
	lua_pop(M,1);	// remove global

	if (lua_getglobal(MainForm->M, "DBLevel") == LUA_TNUMBER) {
		DB->DBLevel->Value = lua_tonumber(MainForm->M, -1);		// Debug Level
		}
	MainForm->GlobalDBLevel = DB->DBLevel->Value;
	lua_pop(M,1);	// remove global
	}

// Auto Connect to the TIU if requested
if (Setup->AutoConnect->Checked) ConnectButton->OnClick(ConnectButton);
// Auto Show the Hot Buttons if requested
if (Setup->AutoHotButtons->Checked) HotButtonClick(HotCommandButton);
//
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CloseAllWindows(void)
{
const TCursor OldCursor = Screen->Cursor; 	// save cursor
Screen->Cursor = crHourGlass;

// stop the single timer thread
if (ptrTimer_Thread) {
	ptrTimer_Thread->StopRequest();
    TimerThreadLED->Checked = false;
    }
ptrTimer_Thread = NULL;

// Close manually created form for each TIU/AIU pair and Conventional Channel, DCS setup, and LashUp
try {

    for (int iPage = 1; iPage <= QUICKPAGEMAX ; iPage++) { // Quick Controls 1-5
        try {
            if (ptrQuick[iPage - 1] != NULL) {
                delete ptrQuick[iPage - 1] ;
                ptrQuick[iPage - 1]  = NULL;
                }
            }
        catch (...) {
            CaughtErrors->Value ++;
            DebugMessage("Failed to deallocate Quick Control Windows");
            }
        }

	for (int iTIU = 0; iTIU < TIUMAX ; iTIU++) { // TIU 1-5
    	try {
			if (ptrDCS[iTIU] != NULL) {
	    		delete ptrDCS[iTIU];
	    	    ptrDCS[iTIU] = NULL;
	    		}
			if (ptrWatch[iTIU] != NULL) {   // Watchdog Signal Windows
	    		delete ptrWatch[iTIU];
	    	    ptrWatch[iTIU] = NULL;
	    		}
            }
        catch (...) {
	        CaughtErrors->Value ++;
        	DebugMessage("Failed to deallocate DCS Setup Windows");
        	}

    	assert(ConvMode == NULL);	// this pointer must not be used
        try {
            for (int iConv = 0 ; iConv < CONVMAX ; iConv++) {
    			if (ptrConvMode[iTIU][iConv] != NULL) {	// Var Channel
	        		delete ptrConvMode[iTIU][iConv];
	        	    ptrConvMode[iTIU][iConv] = NULL;
	    	    	}
                }
            }
        catch (...) {
	        CaughtErrors->Value ++;
        	DebugMessage("Failed to deallocate Track Control Windows");
        	}

        try {
		   	for (int iAIU = 0 ; iAIU < AIUMAX ; iAIU++) { 	// AIU 1-5
		       	if (ptrAIUControl[iTIU][iAIU] != NULL) {
		           	delete ptrAIUControl[iTIU][iAIU];
		            ptrAIUControl[iTIU][iAIU] = NULL;
		            }
		        }
            }
        catch (...) {
	        CaughtErrors->Value ++;
        	DebugMessage("Failed to deallocate AIU Windows");
			}
	    }
    }
catch (...) {
    CaughtErrors->Value ++;
    DebugMessage("Failed to deallocate TIU arrays");
	}
// Close hot button windows
try {
	for (int iHot = 0; iHot < HOTPAGEMAX ; iHot++) { // Hot button pages 1-5
        try {
			if (ptrHot_Command[iHot] != NULL) {	// Hot Buttons - up to 5
	    		delete ptrHot_Command[iHot];
	    	    ptrHot_Command[iHot] = NULL;
	    		}
            }
        catch(...) {
	        CaughtErrors->Value ++;
        	DebugMessage("Failed to deallocate Hot Button Screens");
        	}
        }
    }
catch (...) {
    CaughtErrors->Value ++;
    DebugMessage("Failed to deallocate Hot Button Screens");
	}

// Close manually created forms for each LashUp
try {
    for (int iEng = LASHUPMIN; iEng < ABSOLUTEMAX ; iEng++) {	// 101 to 120 -  to end of LashUps
        if (ptrVolume[iEng] != NULL) {
            delete ptrVolume[iEng];
            ptrVolume[iEng] = NULL;
            }
        if (ptrOp[iEng] != NULL) {
            delete ptrOp[iEng];
            ptrOp[iEng] = NULL;
            }
        }
    for (int iEng = LASHUPMIN; iEng < ABSOLUTEMAX ; iEng++) {	// 101 to 120 -  to end of LashUps

        if (ptrLUEngineList[iEng] != NULL) {
            delete[] ptrLUEngineList[iEng]; 	// pointer to a char array
            ptrLUEngineList[iEng] = NULL;
            }
        if (ptrLUNameList[iEng] != NULL) {
            delete[] ptrLUNameList[iEng]; 	// pointer to a char array
            ptrLUNameList[iEng] = NULL;
            }
        }
    }
catch (...) {
    CaughtErrors->Value ++;
    DebugMessage("Failed to deallocate LashUp Operation & Sound Control Screens");
	}

// Close manually created Program Control Forms and Event FIFOs
try {
	for (int iPCNo = 1; iPCNo <= PCMAX ; iPCNo++) { // Program Control Windows 1-PCMAX
		if (ptrProgram_Control[iPCNo-1] != NULL) {
    		delete ptrProgram_Control[iPCNo-1];
        	ptrProgram_Control[iPCNo-1] = NULL;
        	}
		if (Event_FIFO[iPCNo-1] != NULL) {
    		delete Event_FIFO[iPCNo-1];
        	Event_FIFO[iPCNo-1] = NULL;
        	}
        }
    }
catch (...) {
    CaughtErrors->Value ++;
    DebugMessage("Failed to deallocate Program Control Screens");
	}

// Close manually created forms for each Engine
//assert(Volume == NULL);	// this pointer must not be used
//assert(Op == NULL);		// this pointer must not be used
try {
    for (int iEng = 0; iEng < LASHUPMIN ; iEng++) {	// Engine 0 to 100 - factory reset to All Engine Operation
        if (ptrVolume[iEng] != NULL) {
            delete ptrVolume[iEng];
            ptrVolume[iEng] = NULL;
            }
        if (ptrOp[iEng] != NULL) {
            delete ptrOp[iEng];
            ptrOp[iEng] = NULL;
            }
        if (ptrLUEngineList[iEng] != NULL) {
            delete[] ptrLUEngineList[iEng]; 	// pointer to a char array
            ptrLUEngineList[iEng] = NULL;
            }
        if (ptrLUNameList[iEng] != NULL) {
            delete[] ptrLUNameList[iEng]; 	// pointer to a char array
            ptrLUNameList[iEng] = NULL;
            }
        if (ptrEngineInfo[iEng] != NULL) {
            delete ptrEngineInfo[iEng];
            ptrEngineInfo[iEng] = NULL;
            }
        if (ptrTrackSignal[iEng] != NULL) {
            delete ptrTrackSignal[iEng];
            ptrTrackSignal[iEng] = NULL;
            }
        if (ptrEngineStatus[iEng] != NULL) {
            delete ptrEngineStatus[iEng];
            ptrEngineStatus[iEng] = NULL;
            }
        }
    }
catch (...) {
	CaughtErrors->Value ++;
    DebugMessage("Failed to deallocate Engine Operation and Sound Controls Screens");
	}

Screen->Cursor = OldCursor;
return;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::FormClose(TObject *Sender, TCloseAction &Action)
{
// Note: this function does not run when Terminate() is called. Does run when I press the [Exit] button
// which calls Close();
//
// Save the Engine specific data to a disk file - In WTIU WiFi Mode, this is only place a save is done
SaveEngineData(EngineData);   // sets bTIUDataLoaded to false

if (DisconnectButton->Enabled) DisconnectButton->OnClick(DisconnectButton);	// simulate the user pressing the Disconnect Button

if (bSaveRegistryInfo) {
	// write preferences to the Registry
	TRegIniFile * const RegFile = new TRegIniFile(RegFileRootKey);
	assert(RegFile != NULL);			// make sure that construction worked
	__try {
		RegFile->WriteInteger("Options", "TIU", TIUSpin->ValueAsInt);
		RegFile->WriteInteger("Options", "Remote", RemoteSpin->ValueAsInt);
    	RegFile->WriteInteger("Options", "LastHotComPage", LastHotComPage);
	    // EngineNo saved by SaveEngineData();
        // Save Window Position
		RegFile->WriteInteger("Options", "Top", Top > 0 ? Top : 0);
        RegFile->WriteInteger("Options", "Left", Left > 0 ? Left : 0);
        // Window size is automatic and not saved
	    // Global over all TIU
		RegFile->WriteInteger("Options", "PlainTextFile", PlainTextFile->Checked);
		RegFile->WriteInteger("Options", "TIULog", TIULog->Checked);
		RegFile->WriteInteger("Options", "RFIDLog", RFIDLog->Checked);
		RegFile->WriteInteger("Options", "CRCSel", CRCSel->ItemIndex);
		RegFile->WriteString("WiFi", "WorkingDir" + String(IntToHex(__TCPLUSPLUS__, 4)), WorkingDir);
		RegFile->WriteInteger("Options", "SS", iSS);
		// Engine RAM Dump
	    RegFile->WriteInteger("Debug", "LastLine", LastLine->ValueAsInt);
    	RegFile->WriteInteger("Debug", "FirstLine", FirstLine->ValueAsInt);
    	RegFile->WriteInteger("Options", "LastHotComPage", LastHotComPage);
		//
	    }
	__finally {
		// Deallocate RegFile
		delete RegFile;
		}
	}
// Save strings from Combo Boxes to disk unless the files already exist
if (!FileExists(String(cStartDir) + "\\" + CarModelsFileName)) CarModelComboBox->Items->SaveToFile(String(cStartDir) + "\\" + CarModelsFileName);
if (!FileExists(String(cStartDir) + "\\" + EngineModelsFileName)) EngineModelComboBox->Items->SaveToFile(String(cStartDir) + "\\" + EngineModelsFileName);
if (!FileExists(String(cStartDir) + "\\" + RailroadNamesFileName)) RailroadNameComboBox->Items->SaveToFile(String(cStartDir) + "\\" + RailroadNamesFileName);
if (!FileExists(String(cStartDir) + "\\" + EngineTypesFileName)) EngineTypeComboBox->Items->SaveToFile(String(cStartDir) + "\\" + EngineTypesFileName);
if (!FileExists(String(cStartDir) + "\\" + TagLocationsFileName)) TagLocationComboBox->Items->SaveToFile(String(cStartDir) + "\\" + TagLocationsFileName);
//
// close configuration script
if (M != NULL) {
	lua_close(M);
    M = NULL;
    }
//
CloseAllWindows();
//
Action = caFree; //The form is closed and all allocated memory for the form is freed
return;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ConnectButtonClick(TObject *Sender)
{
//
// Connect Sequence
//
// 0. Initialization
// 1. if WTIU mode, check that at least one WTIU was found
// 2. Start Dispatch List Thread
// 3. Start Communications Thread
// 4. if Radio COM, connect to COMx port
// 5. if RFID COM, connect to COMy port
// 6. if RFID udp WiFi, start RFIDudpRX
// 7. if RFID tcp WiFi, start RFIDtcpRX
// 8. Start mDNS service advertiser (RTCMaster and RTCHost)
// 9. Wait for FIFO to be created by Comm Thread
// 10. setup check boxes for selected modes
// 11. Connect to TIU or WTIU by GetTIUNoAIUCount() and GetTIUVer()
// 12. Find all engines with I0 command and set engines in the Engine List either active or inactive
//
// The Connect Button is disabled once the connection is successful
// The Connect Button is enabled at program start and after the Disconnect Button is pressed
//
// All requested connections must be successful for RTC to work
//
// https://stackoverflow.com/questions/31649315/c-builder-button-click-function-reentry
LOCKEM()

assert(Setup != NULL);		// Setup must be created
assert(DB != NULL);         // Debug Controls must be created
// Process what was found on the command line
if (DebugCommandLine) DebugButton->OnClick(DebugButton);

TMessagePopup *MessagePopup = NULL;

// at least one TIU must be selected (TIU 1 is the default)
if (!(TIUSelection[0] || TIUSelection[1] ||TIUSelection[2] ||TIUSelection[3] ||TIUSelection[4])) {
    DebugMessagePopup("Error", TIUSelMsg, "");
    return;
    }

    // Status bar
StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "";
StatusBar->Panels->Items[RFID_PORT_STATUS]->Text = "";
StatusBar->Panels->Items[TIU_STATUS]->Text = "";

if (Setup->Radio_COM_Mode->Checked) {
	if (IsRadioCOMConnected()) {
   		StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = ConnectedWiFiRadioLED->Caption + " Already connected";
		if (DEBUGGING) DB->Debug_Print("Connect Button: " + ConnectedWiFiRadioLED->Caption + "  Already connected");
        return;
    	}
    }
if (Setup->RFID_COM_Mode->Checked || Setup->RFID_WiFi_Mode->Checked) {
	if (bRFIDCOMConnected || bRFIDUDPWiFiConnected || bRFIDTCPWiFiConnected) {
    	StatusBar->Panels->Items[RFID_PORT_STATUS]->Text = ConnectedRFIDLED->Caption + "  Already connected";
		if (DEBUGGING) DB->Debug_Print("Connect Button: " + ConnectedRFIDLED->Caption + "  Already connected");
        return;
        }
    }
if (IsTIUConnected()) {
   	StatusBar->Panels->Items[TIU_STATUS]->Text = "TIU Already connected";
	if (DEBUGGING) DB->Debug_Print("Connect Button: TIU Already connected");
    return;
    }


// -----------------------------------------------------------------------------
//
// 0. ---------------- Connect :  Initialization ---------------------------
//
StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "Initialization";
CTS_Count = DSR_Count = CD_Count = 0;
CTS_Counter->Text = String(CTS_Count);
DSR_Counter->Text = String(DSR_Count);
CD_Counter->Text = String(CD_Count);
//iRFIDbufCount = 0;	// reset RFID Serial Port Byte counters
TIUSpin->ColorDefault = clYellow;	// set TIU/AIU boxes color to yellow indicating unknown comm with the TIU
AIUSpin->ColorDefault = clYellow;

DisconnectRequest = false;

const TCursor OldCursor = Screen->Cursor; 	// save cursor
Screen->Cursor = crHourGlass;

// --------------------- Exception Handling ------------------------------------
try {
// -----------------------------------------------------------------------------
//
// 1. ---------------- Connect :  mDNS Scan for WTIU ---------------------------
//
if (Setup->WTIU_WiFi_Mode->Checked) {
    StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "mDNS Scan for WTIU";
    // every time that the WTIU boots, it uses a different TCP port.
    // We could save the port number between runs but that does not help.
    // The only solution is to do an mDNS scan for a WTIU every time
    // we start RTC. This scan is done in the Setup contructor.
    // Here we make sure that at least 1 WTIU was found
    //
    if (Setup->WTIUListBox->Items->Count == 0) {
       	DebugMessagePopup("Error", "No WTIU found, use the Setup window to [Scan] for WTIU Ports", "");
        Screen->Cursor = OldCursor;
        StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "No WTIU found";
        return;
        }
    }

// -----------------------------------------------------------------------------
//
// 2. --------------- Connect : Dispatch List Thread ---------------------------
//
// start Dispatch_List_Thread suspended and then start it running
//
StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "Start Dispatch FIFO";
if (!ptrDL_Thread) {
	ResetEvent(TerminationDLEvent);	// reset any lingering events
    // Create suspended - in BCB 4.0, the thread constructor does not wait for lower level constructors itself.
    // The Execute() routine actually runs before some lower level construction is finished.
    // Don't put constructions in the thread constructor. if you have to, start the thread suspended and then start it running
    // per Remy LeBeau (TeamB) it is a Known bug that was fixed in BCB 6.0
    ptrDL_Thread = new TDispatch_List_Thread(true, this, Handle, RemoteNo);
    assert(ptrDL_Thread);	// Check that constructor worked
    DLThreadLED->Checked = true;	// light Dispatch List LED
    #if __BORLANDC__ >= 0x720
    ptrDL_Thread->Start();			// start the thread for CB10.1
    #else
    ptrDL_Thread->Resume();			// start the thread for BCB4.0 - use deprecated in CB10.1, use Start() instead
    #endif
    }
// -----------------------------------------------------------------------------
//
// 3. -------------- Connect : Communications Thread ---------------------------
//
StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "Start Comm Thread";
if (!ptrTIUCommThread) {
    int retv;
    ResetEvent(TerminationCTEvent);	// reset any lingering events
    WTIUSessionsAttemptComplete = false;
    WTIUSessionsConnected = false;
    // Start the Comm Thread (for both Serial and WiFi communications)

    // bTIUSerialPortConnected is set to true by the ISR from the connect() call in CommThread
    //
    // Create (but don't run) the Communications Thread
    // Create suspended - in BCB 4.0, the thread constructor does not wait for lower level constructors itself.
    // The Execute() routine actually runs before some lower level construction is finished.
    // Don't put constructions in the thread constructor. if you have to, start the thread suspended and then start it running
    // per Remy LeBeau (TeamB) it is a Known bug that was fixed in BCB 6.0
    ptrTIUCommThread = new TCommThread(true, this, Handle, ConnectedWiFiRadioLED->Caption,
            Baud[Setup->BaudRateSel->ItemIndex],
            Parity[Setup->ParitySel->ItemIndex],
            Bits[Setup->BitsSel->ItemIndex],
            Stop[Setup->StopSel->ItemIndex],
            Setup->Radio_COM_Mode->Checked,
            Setup->WTIU_WiFi_Mode->Checked,
            Setup->RFID_COM_Mode->Checked,
            Setup->RFID_WiFi_Mode->Checked,
            Setup->AIU_WiFi_Mode->Checked,
            Setup->Signals_WiFi_Mode->Checked,
            &retv);
    assert(ptrTIUCommThread);	// Check that constructor worked
    CommThreadLED->Checked = true;	// light Comm Thread LED Yellow and will go Green when Execute() runs
    if (ptrTIUCommThread) {
      // Successful Open
        #if __BORLANDC__ >= 0x720
        ptrTIUCommThread->Start();			// start the thread for CB10.1
        #else
        ptrTIUCommThread->Resume();			// start the thread for BCB4.0 - use deprecated in CB10.1, use Start() instead
        #endif
        }

    // if the WiFi connections made by the CommThread are not successful, then
    // disconnect from everything, set LED to yellow, restore the cursor, and exit
    if (Setup->WTIU_WiFi_Mode->Checked) {
        while (!WTIUSessionsAttemptComplete) {   // wait for all of the attempts to complete or time out
            Application->ProcessMessages();
            }
        if (!WTIUSessionsConnected) {            // connect failed
            Screen->Cursor = OldCursor;
            DisconnectButtonClick(Sender);
            CommThreadLED->LED->ColorOn = clYellow;
            StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "";
            return;
            }
        // At this point, all selected WTIU are on-line and have completed the Handshake
        // Non-selected WTIU have been disconnected
        }
    // update the version number in the Connect button.
    TIUSpinValueChanged(Sender);
    }
// -----------------------------------------------------------------------------
//
// 4. -------------------- Connect : Serial Radio ------------------------------
//
if (Setup->Radio_COM_Mode->Checked && !IsRadioCOMConnected()) {
    StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "Start Radio";
	// connect Radio Serial Communication Port
    //
    // note that port must be specified as       "\\\\.\\COM10"
    // see https://support.microsoft.com/en-us/kb/115831
    char ComName[10];
	snprintf(ComName, sizeof ComName, "COM%d", Setup->RadioCOMPortNum->ValueAsInt);
    const String MungedPortName = "\\\\.\\" + String(ComName);	// pass in communications port

    // Return 0 on success, > 0 on failure.
    int retv = Serial_port->connect(MungedPortName.c_str(), Baud[Setup->BaudRateSel->ItemIndex],
    	Parity[Setup->ParitySel->ItemIndex], Bits[Setup->BitsSel->ItemIndex], Stop[Setup->StopSel->ItemIndex], true);
    if (retv) {
        // failure
	    StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "Failed to open " + String(ComName) + " (" + String(retv) + ")";
	    DebugMessage("Failed to open " + String(ComName) + " (" + String(retv) + ")");
        Screen->Cursor = OldCursor;
        DisconnectButtonClick(Sender);
        CommThreadLED->LED->ColorOn = clYellow;
        return;
        } else {
        // Successful Open - wait for it to complete
#ifdef OLDWAY
        while(!bRadioCOMConnected) {
        	Sleep(10);
        	Application->ProcessMessages();
            }
#else
        // suggested by ChatGPT
        DWORD Start = GetTickCount();

        while(!bRadioCOMConnected) {
            if (GetTickCount() - Start > 10000) {
                DebugMessage("Timeout waiting for TIU Radio Communications");
                Screen->Cursor = OldCursor;
                DisconnectButtonClick(Sender);
                return;
                }

        	Sleep(10);
        	Application->ProcessMessages();
            }
#endif
        StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "Radio Serial Connected";
        // DTR and RTS start in off state
        Serial_port->DTR(false);
        Serial_port->RTS(false);
        // TODO : Feather 32U4 Support - DTR must be set when using the Feather with 32U4 processor
		//DTRSwitch->Checked = Setup->DTRAlwaysOn->Checked;		// On my Wired interface, DTR enables the Hex Buffer
        								// Setting DTR Switch to true resets the Arduino each time the COM port is opened.
                                        // This causes the first packet to be ignored and always timesout and gets an error
                                        // but then the retry is always successful.
        // Set DTR if requested
	    //if (DTRSwitch->Checked && !WIREDMODE) {
        //	ptrTIUCommThread->DTRSwitch(true);
        //    }
        //if (Setup->DTRAlwaysOn->Checked) {
        //	Delay(2000);	// wait for DTR to take effect when using the Feather with 32U4 processor
        //    }
        //
        }
	// bRadioCOMConnected is set to true by the ISR from the connect() call
	}
// -----------------------------------------------------------------------------
//
// 5. ---------------- Connect : Serial RFID Detectors -------------------------
//
if (Setup->RFID_COM_Mode->Checked && !IsRFIDCOMConnected()) {
    StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "Start RFID Radio";
	// connect RFID Serial  Port if so selected
    // note that port must be speicfied as       "\\\\.\\COM10"
    // see https://support.microsoft.com/en-us/kb/115831
    // bRFIDSerialPortConnected is set to true by the ISR
    char RFIDName2[10];
	snprintf(RFIDName2, sizeof RFIDName2, "COM%d", Setup->RFIDCOMPortNum->ValueAsInt);

    const String MungedPortName = "\\\\.\\" + String(RFIDName2);

    int retv = RFID_serial_port->connect(MungedPortName.c_str(),
            TAG_READER_BAUD_RATE,								// always 115200 baud
            Parity[Setup->ParitySel->ItemIndex],
            Bits[Setup->BitsSel->ItemIndex],
            Stop[Setup->StopSel->ItemIndex],
            true);
    if (retv) {
        // failure
        StatusBar->Panels->Items[RFID_PORT_STATUS]->Text = "Failed to open " + String(RFIDName2) + " (" + String(retv) + ")";
        DebugMessage("Failed to open " + String(RFIDName2) + " (" + String(retv) + ")");
        Screen->Cursor = OldCursor;
        DisconnectButtonClick(Sender);		// make sure that ports are disconnected
        CommThreadLED->LED->ColorOn = clYellow;
        // enable Exit escape
        ExitButton->Enabled = true;
        return;
        } else {
        // else Successful Open
        // the serial port driver turns off DTR and RTS to set the respective lines
        RFID_serial_port->DTR(false);
        RFID_serial_port->RTS(true);
        // Toggle DTR to reset the Arduino
        RFID_serial_port->DTR(true);
        Send_ASCII_TIU->Enabled = true;
        Send_Hex_RFID->Enabled = true;
        //if (ptrProgram_Control[0] ) ptrProgram_Control[0]->RFIDLED->Checked = true;
        StatusBar->Panels->Items[RFID_PORT_STATUS]->Text = "RFID Serial Connected";
        }
	 // bRFIDCOMConnected is set to true by the ISR from the connect()
     }
//
if (Setup->RFID_COM_Mode->Checked || Setup->Radio_COM_Mode->Checked) {
    // give up to 250 ms for the serial ports to connect
    // We look at the radio port and the RFID port if they are enabled in Setup
    for (int step = 250/50; step > 0 ; step--) {
        Delay(50);	// delay for 50 ms
        /*
		if ((!Setup->Radio_COM_Mode->Checked || bRadioCOMConnected) && (!Setup->RFID_COM_Mode->Checked || bRFIDCOMConnected)) {   // check flags set by ISR
        	// was 	if (bTIUSerialPortConnected && (!Setup->RFID_Mode->Checked || bRFIDSerialPortConnected)) {   // check flag set by ISR
            //
            // start idle timer if requested to drop DTR or RTS after 10 seconds of idle
            // Don't drop DTR in TIU Emulator mode because DTR is used as Reset of Arduino
            //
            if ((Setup->DropDTRonIdle->Checked  && (!TIUEMULATORMODE && !RADIOMODE)) || Setup->DropRTSonIdle->Checked) {
                IdleTimer->Enabled = true;
                IdleTimer->Tag = 0;	// reset Timer Counter
                } else {
                IdleTimer->Enabled = false;
                }
            }
        */
        }
    }

// -----------------------------------------------------------------------------
//
// 6. & 7. --------- Connect : WiFi RFID UDP and TCP ---------------------------
//
if (Setup->RFID_WiFi_Mode->Checked && !bRFIDUDPWiFiConnected && !bRFIDTCPWiFiConnected) {
    StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "Start RFID WiFi";
	if (DEBUGGINGX(5)) DB->Debug_Print("RFID Local Host Name = " + LocalHostName());
    if (DEBUGGING) DB->Debug_Print("Selected WiFi RFID Interface = " + Setup->IPComboBox->Text);
    // ---------------------------------------------------------------
    //
    // 7. --------------------------- WiFi RFID UDP ----------------------------
    //
	RFIDudpRX->Proto    = "udp";
	RFIDudpRX->Addr     = "0.0.0.0";	// Listen on all available interfaces simultaniously
	RFIDudpRX->Port     = 8899;
    RFIDudpRX->ReuseAddr = true;

	try {
		RFIDudpRX->Listen();
#ifdef OLDWAY
		while(bRFIDUDPWiFiRXConnected == false) {
			Sleep(10);
            RFIDudpRX->ProcessMessages();
			}
#else
        // suggested by ChatGPT
        DWORD Start = GetTickCount();

        while (!bRFIDUDPWiFiRXConnected) {
            if (GetTickCount() - Start > 10000) {
                DebugMessage("Timeout waiting for RFID UDP WiFi Communications");
                Screen->Cursor = OldCursor;
                DisconnectButtonClick(Sender);
                return;
                }

            Sleep(10);
            RFIDudpRX->ProcessMessages();
            }

#endif
        if (DEBUGGING) DB->Debug_Print("RFID RX WiFi listening on UDP Port " + RFIDudpRX->Port);
        StatusLED1->Checked = true;
		} catch (const ESocketException &E) {
		    // The socket is probably already in use
			if (DEBUGGING) DB->Debug_Print("RFID RX WiFi error on UDP Port : " + E.Message);
			throw;
		}

    // ---------------------------------------------------------------
    //
    // 8. -------------------- Connect : WiFi RFID TCP -------------------------
    //
    // Code from TCPICSTest.cpp
    RFIDtcpRX->ClientClass = __classid(TTcpSrvClient); // Use our component
    RFIDtcpRX->Banner      = "";
    RFIDtcpRX->Proto       = "tcp";     // Use TCP protocol
    RFIDtcpRX->Port        = 8897;
    RFIDtcpRX->Addr        = "0.0.0.0"; // Use any interface
    RFIDtcpRX->ListenBacklog   = 100;   // maximum backlog of clients waiting to connect
    RFIDtcpRX->MaxClients      = 100;   // maximum number of detectors
	RFIDtcpRX->ReuseAddr = true;

    if (DEBUGGING) DB->Debug_Print("RFID WiFi TCP Server Port = " + RFIDtcpRX->Port);
    RFIDtcpRX->Listen();                    // Start listening
    bRFIDTCPWiFiConnected = true;
    StatusLED2->Checked = true;
    if (DEBUGGING) DB->Debug_Print("Listening for RFID WiFi TCP clients...");

    StatusBar->Panels->Items[RFID_PORT_STATUS]->Text = "RFID WiFi TCP Connected";
    }
// -----------------------------------------------------------------------------
//
// 8. ------ Connect :  Start mDNS Server ---  (RTCMaster and RTCHost) ---------
//
StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "Start mDNS Server";
//if (Setup->RFID_WiFi_Mode->Checked || Setup->WTIU_WiFi_Mode->Checked || Setup->AIU_WiFi_Mode->Checked || Setup->Signals_WiFi_Mode->Checked) {
if (Setup->RFID_WiFi_Mode->Checked) {
    // Start the mDNS responder - mDNS query if in WiFi mode
    Setup->StartmDNSServices(Setup->RFID_WiFi_Mode->Checked, Setup->WTIU_WiFi_Mode->Checked);
    }
// -----------------------------------------------------------------------------
//
// 9. Wait for FIFO to be created which also indicates that all TIU are connectged
//
StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "Start Comm FIFO";
while (!ptrTIUCommThread->FIFO) {
	Sleep(10);
    Application->ProcessMessages();
    }
// -----------------------------------------------------------------------------
//
// 10. -- Connect : Setup Check Boxes Disable when Connected via Radio or WiFi ------
//
if (IsRadioConnected()) {
    StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "Disable Setup";
    ConnectedWiFiRadioLED->Checked = true;
    // Disable most Serial port setup controls while we are connected
    Setup->RFID_COM_Mode->Enabled = false;
    Setup->Radio_COM_Mode->Enabled = false;
    Setup->RFID_WiFi_Mode->Enabled = false;
    Setup->WTIU_WiFi_Mode->Enabled = false;
    Setup->RadioCOMPortNum->Enabled = false;
    Setup->RFIDCOMPortNum->Enabled = false;
    Setup->BaudRateSel->Enabled = false;
    Setup->ParitySel->Enabled = false;
    Setup->BitsSel->Enabled = false;
    Setup->StopSel->Enabled = false;
    //
    Send_ASCII_TIU->Enabled = true;
    Send_Hex_TIU->Enabled = true;
    // enable controls on successful Serial Port connection
    RTSSwitch->Enabled = true;
    DTRSwitch->Enabled = true;
    STOPem->Enabled = false;
    StartedConsists->Value = 0;
    StartupButton->Enabled = true;
    QuickButton->Enabled = true;
    Var1ModeButton->Enabled = true;
    Var2ModeButton->Enabled = true;
    ShutdownButton->Enabled = true;
    ReadEnginesButton->Enabled = true;
    // Program control is allowed even if the RFID tag system is not used
    Prog_Control_Button->Enabled = true;

    // enable Accessories/Switches Button & Stop All Button
    StopAllButton->Enabled = true;
    // enable LashUp buttons
    LashUp->LashUpShutdownButton->Enabled = true;
    }
// -----------------------------------------------------------------------------
//
// 11. --------- Connect : TIU or WTIU and set Super TIU Mode ------------------
//
// check that we are connected to at least one TIU
StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "Check TIU Connection";
// Radio Mode
if (Setup->Radio_COM_Mode->Checked) {
    // loop over TIUMAX number of TIU
    for (int iTIU = 1; iTIU <= TIUMAX; iTIU++) {
        if (TIUSelection[iTIU - 1] == false) continue;  // no TIU attached to this TIU number
        // Try to connect to TIU via radio
        bool bretv = ConnecttoTIU(iTIU);
        if (!bretv) {
            DisconnectButtonClick(ConnectButton);
            Screen->Cursor = OldCursor;
            StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "";
            return;
            }
        }
    }

// WiFi Mode
if (Setup->WTIU_WiFi_Mode->Checked) {
	// connection to WTIU is made by the Comm_Thread
	if (SelectedWTIUPortCount > 0) {
        TIUSpinChange(NULL);    // set spinner color
        //AIUSpin->ValueAsInt = AIUCount[TIUSpin->ValueAsInt - 1];
        if (AIUCount[TIUSpin->ValueAsInt - 1] > 0) {
            AIUSpin->ValueAsInt = 1;    // set to first AIU
            } else {
            AIUSpin->ValueAsInt = 0;    // no AIU
            }
        AIUSpin->MaxValue = AIUCount[TIUSpin->ValueAsInt - 1];
        DisconnectButton->Enabled = true;
        ConnectButton->Enabled = false;	// on success, disable connect button

        ExitButton->Enabled = false;
		if (DEBUGGINGX(3)) DB->Debug_Print("Connect to WTIU : successful");
		// Auto Show the Quick Controls if requested
        if (Setup->AutoQuickControls->Checked) QuickButtonClick(QuickButton);
        //
        StatusBar->Panels->Items[TIU_STATUS]->Text = "WTIU Connected";

        ConnectedTIULED->Checked = true;
        ConnectedTIULED->Caption = "TIU";

        // disable Exit escape if connection was successful
        ExitButton->Enabled = false;
		MainForm->Caption = MainForm->SavedCaption + " : " + String(SelectedWTIUPortCount) + " TIU(s) Connected";   // put the TIU count in the Caption

		} else {
        AIUSpin->MaxValue = AIUMAX; // Failed to get AIU Count
        AIUCount[TIUSpin->ValueAsInt - 1] = AIUMAX;
        AIUSpin->ValueAsInt = 1; // Failed to get AIU Count
        //
        TIUSpin->ColorDefault = clYellow;
        AIUSpin->ColorDefault = clYellow;
        // enable Exit escape if connection was unsuccessful
        if (DEBUGGINGX(3)) DB->Debug_Print("Connect to TIU : failed");

        ExitButton->Enabled = true;
        // indicate disconnected from the TIU
        StatusBar->Panels->Items[TIU_STATUS]->Text = "TIU Disconnected";
        ConnectedTIULED->Checked = false;
        MainForm->Caption = MainForm->SavedCaption + " : TIU(s) Disconnected";   // put the TIU count in the Caption
        }
    }

if (SuperTIU) SuperTIU->SuperPanel->Enabled = DisconnectButton->Enabled;
if (ActiveTIU) ActiveTIU->ActivePanel->Enabled = ConnectButton->Enabled;

if (SuperTIUMode && ConnectedTIULED->Checked && Setup->WTIU_WiFi_Mode->Checked) { // set Super TIU mode in each TIU as necessary
    // In WiFi Mode, send a 'B' command to the WTIU. This makes sure that RTC and the TIU are synchronized
    for (int MyTIUNo = 1 ; MyTIUNo <= TIUMAX ; MyTIUNo++) {
        if (TIUSelection[MyTIUNo - 1]) {    // Is the TIU active?
            char BCommand[10];
            if (TIUSuperMode[MyTIUNo - 1]) {          // Is the TIU set in Super TIU Mode?
                sprintf(BCommand,"B1%d", MyTIUNo - 1);
                } else {
                sprintf(BCommand,"B%d", MyTIUNo - 1);
                }
            (void)ptrTIUCommThread->Send_String_FIFO(FACTORYRESET, BCommand, MyTIUNo, MainForm->RemoteNo, 11, SHORT_WAIT * 20, RECORD_OK);
            }
        }
    }    
// -----------------------------------------------------------------------------
//
// 12. ------------------ Connect : Find Engines on Layout ---------------------------
//
if (ConnectedTIULED->Checked && Setup->AutoFindEngines->Checked) {
    StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "Finding Engines";
    MessagePopup = NULL;  // ReadEnginesButtonClick

    Application->ProcessMessages(); // process all messages before the popup
    // please wait message
    if (!MessagePopup) {
        MessagePopup = new TMessagePopup(this, Handle, NULL, NULL);     // Pass "this" as the owner
        assert(MessagePopup != NULL);		// make sure that construction worked
        }
    MessagePopup->Show();
    MessagePopup->Caption = "Information";
    MessagePopup->MessageLine->Caption = "  Finding Engines  ";
    if (DEBUGGING) DB->Debug_Print(MessagePopup->Caption + " : " + MessagePopup->MessageLine->Caption + " - " + MessagePopup->StatusLine->Caption);
    MessagePopup->Repaint();

    FindEngines();    // on all TIU

    if (MessagePopup != NULL) {
        MessagePopup->Hide();
        delete MessagePopup;
        MessagePopup = NULL;
        }
    }
//
StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "Comm Established";
Screen->Cursor = OldCursor;
return;
}
// --------------------- Exception Handling ------------------------------------
// -----------------------------------------------------------------------------
catch(const ESocketException &E) {
    if (MessagePopup != NULL) {
        MessagePopup->Hide();
        delete MessagePopup;
        MessagePopup = NULL;
        }

    DebugMessage("Exception occured while attempting to Connect to TIU");
    DisconnectButtonClick(ConnectButton);
    Screen->Cursor = OldCursor;
    StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "Exception while connecting";
    CommThreadLED->LED->ColorOn = clYellow;
    return;
    }
// -----------------------------------------------------------------------------
}
//---------------------------------------------------------------------------
bool __fastcall TMainForm::ConnecttoTIU(int TIU) {
//
if (DEBUGGINGX(3)) DB->Debug_Print("ConnecttoTIU(" + String(TIU) + ")");
//
// Used only in Radio Comm Mode
//
// This next piece of code caused an interesting problem. When I had just the AIUCount
// readup enabled, the TIU would respond correctly the first time. If I just started RTC
// connected, disconnected, exited, restarted, connected - the second connection would fail. This was
// because the sequence number was the same for two packets in a row and the TIU ignored
// the second packet (never responded).
// I attempt to fix that by sending two commands at startup which will bump the sequence number
//
// if shift is pressed, skip these two calls and use debugging data
//
const SHORT ShiftKeyState = ConnectButton->Focused() ? GetKeyState(VK_SHIFT) : 0;	// Do a debug Connect to TIU
// key pressed =  0xFF8X
// key not pressed = 0x000X
// "X" can be 0 or 1 - indicated key was toggled
if (ShiftKeyState >= 0) {
  // if shift is not pressed, do a normal connect
  if (Setup->Hex->Checked && !Setup->TXOnlyComm->Checked) {	// Hex mode & two way comm required
    // For Radio mode, this only gets the number of AIU
    // For WiFi mode, this gets both the TIU Number and the number of AIU
    if (GetTIUNoAIUCount(TIU)) { //if successful, set bTIUConnected to true
        // if the GetTIUNoAIUCount worked, read up the TIU version number - needed to form LashUp command correctly
        if (GetTIUVer(TIU)) {	// this also bumps the sequence number
        // success with above two calls will set TIUSpin to clLime
        // and bTIUConnected to true, failure will set TIUSpin to clRed
            // GetTIUVer() failed
            // fall thru to next "if" statement with bTIUConnected == false
            }
        } else {
        // GetTIUNoAIUCount() failed
        // fall thru to next "if" statement with bTIUConnected == false
        }
    }
  } else {
  // if shift is pressed, use debug values
  bTIUConnected = true;
  ConnectedTIULED->Checked = true;
  TIUSpin->ColorDefault = clYellow;
  AIUSpin->ColorDefault = clYellow;
  ConnectButton->Caption = "Debug";
  TIUVersion[TIU - 1] = 610;    // v 6.10
  AIUSpin->ValueAsInt = 1;	// one AIU
  //AIUButton->Enabled = true;
  AIUSpin->MaxValue = AIUMAX;	// debug values - allow all AIU
  AIUCount[TIU - 1] = AIUMAX;	// debug values - allow all AIU
  AIUSpin->Enabled = true;
  StatusBar->Panels->Items[TIU_STATUS]->Text = "TIU Simulated";
  }
// if the Get AIU command was successful, move forward
if (IsTIUConnected()) {
    DisconnectButton->Enabled = true;
    ConnectButton->Enabled = false;	// on success, disable connect button to force user to [Disconnect] before trying to reconnect
    ExitButton->Enabled = false;
    bTIUConnected = true;
    if (DEBUGGINGX(3)) DB->Debug_Print("ConnecttoTIU(" + String(TIU) + ") : successful");
    // Auto Show the Quick Controls if requested
    if (Setup->AutoQuickControls->Checked && IsTIUConnected()) QuickButtonClick(QuickButton);
    //
    StatusBar->Panels->Items[TIU_STATUS]->Text = "TIU Connected";
    ConnectedTIULED->Checked = true;
    ConnectedTIULED->Caption = "TIU";
    if (SuperTIU) SuperTIU->SuperPanel->Enabled = DisconnectButton->Enabled;
    if (ActiveTIU) ActiveTIU->ActivePanel->Enabled = ConnectButton->Enabled;
    return true;    // success
    } else {
    // Failed to get AIU count
    if (DEBUGGINGX(3)) DB->Debug_Print("ConnecttoTIU(" + String(TIU) + ") : failed");
    bTIUConnected = false;
    // indicate disconnected from the TIU
    StatusBar->Panels->Items[TIU_STATUS]->Text = "TIU Disconnected";
    ConnectedTIULED->Checked = false;
    ConnectedTIULED->Caption = "TIU";
    if (SuperTIU) SuperTIU->SuperPanel->Enabled = DisconnectButton->Enabled;
    if (ActiveTIU) ActiveTIU->ActivePanel->Enabled = ConnectButton->Enabled;
    return false;   // failure
    }
// no flow
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ExitButtonClick(TObject *Sender)
{
// TODO : when compiled with BCB4.0, the computer sometimes beeps when the [Exit] button is pressed.
#if __BORLANDC__ >= 0x720 && ENABLENOTIFICATIONS
RTCNotificationCenter->CancelAll();
#endif
Close();    // Close the Form. This calls Application->Terminate()
}
//---------------------------------------------------------------------------
void __fastcall TMainForm::DisconnectButtonClick(TObject *Sender)
{
//
// Disconnect sequence
//
// 0. Initialization
// 1. Save recording, if any
// 2. Request all PC Threads be stopped
// 3. Request all Engine Status and Engine Information Threads be stopped
// 4. Stop all mDNS service advertisers
// 5. Stop RFIDucpRX if running
// 5. Stop RFIDtcpTX if running
// 6. Disconnect TIU or WTIU, clear AIU count
// 7. Stop the Dispatch List Thread
// 8. Stop all playback threads
// 9. if Radio COM, disconnect the COMx port
// 10. if RFID COM, disconnect the COMy port
// 11. if RFID WiFi, disconnect
// 12. Wait for Radio COM and RFID COM ports to disconnect
// 13. Stop the Communications Thread
// 14. setup check boxes to default settings
//

assert(Setup != NULL);		// setup must be created

// https://stackoverflow.com/questions/31649315/c-builder-button-click-function-reentry
LOCKEM()

bEmergencyStop = false; 	// reset emergency stop flag
// -----------------------------------------------------------------------------
//
// 0. Initialization
//
StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "Initialization";
if (iPlayingBackCounter) {
	DebugMessagePopup("Warning", "Cancel the Playback before trying to disconnect", "");
    StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "";
	return;
	}
// Initialize the TIU/WTIU variables
for (int iTIU = 1; iTIU <= TIUMAX ; iTIU++) { // TIU 1-5
    AIUCount[iTIU-1] = 0;
    //AIUEmulator[iTIU-1].Found = false;
    TIUVersion[iTIU-1] = 0;
    TIUVersionString[iTIU-1] = "&Connect";
    ptrWatch[iTIU-1] = NULL;
    }
// Initialize the Signals flags
for (int iSig = 0; iSig < SIGNALMAX ; iSig++) {
    Signals[iSig].Found = false;
    }
// Initialize the RFID Detector flags
for (int iDet = 0; iDet < RFIDMAX ; iDet++) {
    Detector[iDet].Found = false;
    }
// -----------------------------------------------------------------------------
//
// 1. if recording, ask to save the recording
//
StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "Stop Recording";
if (bRecording) {
	Setup->RecordStopButton->OnClick(Setup->RecordStopButton);
	}
// -----------------------------------------------------------------------------
//
// 2. if Program Control is running, ask that the user to [STOP] any scripts before disconnecting
//    Disabled if DEBUGGING
//
StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "Stop PC Windows";
if (!DEBUGGING) for (int iPCNo = 1 ; iPCNo <= PCMAX ; iPCNo++) {
	if (ptrProgram_Control[iPCNo - 1] != NULL) {
		if (ptrProgram_Control[iPCNo - 1]->ptrPC_Thread != NULL) {
			DebugMessagePopup("Warning", "[STOP] any Program Control Scripts before trying to disconnect", "");
            StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "";
			return;
			}
        }
	}
// -----------------------------------------------------------------------------
//
// 3. if any Engine Information or Engine Status threads are running, ask the the user to [STOP] them before disconnecting
//    Disabled if DEBUGGING
//
StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "Stop Engine Into/Status";

if (!DEBUGGING) for (int iEng = 0; iEng < LASHUPMIN ; iEng++) {  	// Engine 0 to 120 - factory reset to end of engines
    if (ptrEngineInfo[iEng]) {
        if (ptrEngineInfo[iEng]->ptrEngineInfoThread != NULL) {
            DebugMessagePopup("Warning", "[Stop] any Engine Information windows before trying to disconnect", "");
            StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "";
            return;
            }
        }
    if (ptrEngineStatus[iEng]) {
        if (ptrEngineStatus[iEng]->ptrEngineStatusThread != NULL) {
            DebugMessagePopup("Warning", "[Stop] any Engine Status windows before trying to disconnect", "");
            StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "";
            return;
            }
        }
    if (ptrTrackSignal[iEng]) {
        if (ptrTrackSignal[iEng]->ptrTrackSignalThread != NULL) {
            DebugMessagePopup("Warning", "[Stop] any Track Signal windows before trying to disconnect", "");
            StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "";
            return;
            }
        }
    }

DisconnectRequest = true;	// indicate that the [Disconnect] button was pressed
// -----------------------------------------------------------------------------
//
// 4. Stop the mDNS Server
//
StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "Stop mDNS Server";

if (Setup->RFID_WiFi_Mode->Checked || Setup->WTIU_WiFi_Mode->Checked || Setup->AIU_WiFi_Mode->Checked || Setup->Signals_WiFi_Mode->Checked) {
    // Stop the mDNS responder/querier if in WiFi mode
    // This formerlly was done in Setup destructor
   	Setup->StopmDNSServices();
    }
// -----------------------------------------------------------------------------
//
// 5. disconnect RFID UDP WiFi
//
if (bRFIDUDPWiFiConnected && RFIDudpRX) {
    StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "Disconnect RFID UDP";
    StatusLED1->Checked = false;
	RFIDudpRX->Close();
    }
// -----------------------------------------------------------------------------
//
// 5. disconnect RFID TCP WiFi
//
if (bRFIDTCPWiFiConnected && RFIDtcpRX) {
    StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "Disconnect RFID TCP";

    TTcpSrvClient *AClient;

    RFIDtcpRX->Close();    // first close the socket server
    // then close all clients (working backwards through the client list)
	int CC = RFIDtcpRX->ClientCount;
	if (CC > 0) {
		for (int I = CC - 1; I >= 0; I--) {
			AClient = (TTcpSrvClient *)(RFIDtcpRX->Client[I]);
			if (DEBUGGING) DB->Debug_Print("Closing " + AClient->PeerAddr);
			AClient->Shutdown(2);
			}
		AClient = NULL;
		}
    bRFIDTCPWiFiConnected = false;
    StatusLED2->Checked = false;
    }

if (Setup->Radio_COM_Mode->Checked && !IsRadioCOMConnected()) {
   	StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = ConnectedWiFiRadioLED->Caption + " Not connected";
	if (DEBUGGING) DB->Debug_Print("Disconnect Button: " + ConnectedWiFiRadioLED->Caption + " Not connected");
    }
if (Setup->RFID_COM_Mode->Checked && !IsRFIDCOMConnected()) {
   	StatusBar->Panels->Items[RFID_PORT_STATUS]->Text = ConnectedRFIDLED->Caption + " Not connected";
	if (DEBUGGING) DB->Debug_Print("Disconnect Button: " + ConnectedRFIDLED->Caption + " Not connected");
    }
if (!IsTIUConnected()) {
   	StatusBar->Panels->Items[TIU_STATUS]->Text = "TIU Not connected";
	if (DEBUGGING) DB->Debug_Print("Disconnect Button: TIU Not connected");
    }

ConnectedWiFiRadioLED->Checked = false;

TIUSpin->ColorDefault = clYellow;	// set TIU/AIU boxes color to yellow indicating unknown comm with the TIU
AIUSpin->ColorDefault = clYellow;

ExitButton->Enabled = false;		// don't allow [Exit] until disconnect is complete

bPlayingBack = bPlaybackCommError = false;		// clear record or playback flags to terminate those operations
iPlayingBackCounter = 0;						// indicate no playbacks are running

bRecording = false;
// -----------------------------------------------------------------------------
//
// 6. if TIU is connected, disconnect it
//
StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "Disconnect TIU";
if (IsTIUConnected()) {
	bTIUConnected = false;    // indicate disconnected
    StatusBar->Panels->Items[TIU_STATUS]->Text = "TIU Disconnected";
    }
ConnectedTIULED->Checked = false;
AIUSpin->ValueAsInt = 0;             // reset AIU Count
if (Setup->RFID_WiFi_Mode->Checked) {
    Caption = MainForm->SavedCaption + " : " + String(SelectedWTIUPortCount) + " TIU(s) Connected";   // put the TIU count in the Caption
    }
// -----------------------------------------------------------------------------
//
// 7 & 8. stop the Dispatch List and Playback Threads
//
StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "Stop Dispatch FIFO";
assert(ptrTIUCommThread);					// must be created
bool bretv = SetEvent(TerminationDLEvent);  		// stop Dispatch List thread -  DL_Thread
assert(bretv);
bretv = SetEvent(TerminationPBEvent);  		// stop any (and all) Playback threads -  PBFileThread
assert(bretv);
DLThreadLED->Checked = false;	// extinquish Dispatch List LED
ptrDL_Thread = NULL;						// FreeOnTerminate is set
//---------------------------------------------------------------------------
//
// 9, if Radio COM port is enabled and connected, disconnect it
//
// bRadioCOMConnected is set to false by the ISR
//
if (Setup->Radio_COM_Mode->Checked && IsRadioCOMConnected()) {
    StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "Disconnect Radio Comm";
	DSR->Checked = false;
	CTS->Checked = false;
   	CD->Checked = false;
    // Disable controls when diconnected
	DTRSwitch->Enabled = false;
    RTSSwitch->Enabled = false;
	if (Setup->NumRetries->Value != 10) {
	    Serial_port->disconnect();					// Serial_port.disconnect() call in Comm_Thread destructor
		//bretv = SetEvent(TerminationCTEvent);  		// destroy Comm thread - Comm_Thread & serial_event
		//assert(bretv);
		//ptrTIUCommThread = NULL;             		// FreeOnTerminate is set
        CommThreadLED->Checked = false;	// extinquish Comm Thread LED
        CommThreadLED->LED->ColorOn = clLime;   // reset color
		if (DEBUGGING) DB->Debug_Print("Disconnect Button: Radio Serial Port Disconnected");
        StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "Radio Serial Disconnected";
        }
	}
//---------------------------------------------------------------------------
//
// 10.  if RFID COM port is enabled and connected, disconnect it
//
// bRFIDCOMConnected is set to false by the ISR
//
if (Setup->RFID_COM_Mode->Checked && IsRFIDCOMConnected()) {
    StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "Disconnect RFID Comm";
    Send_ASCII_RFID->Enabled = false;
	Send_Hex_RFID->Enabled = false;
    /////////
  	if (Setup->NumRetries->Value != 10) {
    	RFID_serial_port->disconnect();			// RFID_serial_port->disconnect() call in Comm_Thread destructor
		//bretv = SetEvent(TerminationCTEvent);  	// destroy Comm thread - Comm_Thread & serial_event
		//assert(bretv);
		//ptrTIUCommThread = NULL;             	// FreeOnTerminate is set
		CommThreadLED->Checked = false;	        // extinquish Comm Thread LED
        CommThreadLED->LED->ColorOn = clLime;   // reset color
		if (DEBUGGING) DB->Debug_Print("Disconnect Button: RFID Serial Port Disconnected");
        StatusBar->Panels->Items[RFID_PORT_STATUS]->Text = "RFID Serial Disconnected";
		}
    }
//---------------------------------------------------------------------------
//
// 11. if RFID WiFi port is enabled, show a disconnect message
//
if (Setup->RFID_WiFi_Mode->Checked) {
    StatusBar->Panels->Items[RFID_PORT_STATUS]->Text = "RFID WiFi Disconnected";
	}

//---------------------------------------------------------------------------
//
// 12. Wait for Radio COM and RFID COM ports to disconnect
//
if (Setup->RFID_COM_Mode->Checked || Setup->Radio_COM_Mode->Checked) {
    //
    // give up to 250 ms for both serial ports to disconnect
    // We look at the radio COM port and the RFID COM port if they are enabled in Setup
    for (int step = 250/50; step > 0 ; step--) {
        Delay(50);	// delay for 50 ms
		if (!bRadioCOMConnected && !bRFIDCOMConnected) {    // check flags set by ISR to be both false
			break;	// out of for() loop as ports are disconnected
            }
        }
	// Serial Port Disconnect timeout if either connected flag is still set
    if (bRadioCOMConnected || bRFIDCOMConnected) {
		if (DEBUGGING) DB->Debug_Print("Disconnect Button: Can't disconnect from the serial ports (Timeout after 250ms)");
        }
    }
if (Setup->RFID_WiFi_Mode->Checked || Setup->WTIU_WiFi_Mode->Checked) {
	// no special waiting is needed for the WiFi Ports but still must terminate the TCommThread
   	if (Setup->NumRetries->Value != 10) {
		if (DEBUGGING) DB->Debug_Print("Disconnect Button: Radio & RFID WiFi Ports Disconnected");
        }
    }
// -----------------------------------------------------------------------------
//
// 13. Terminate the TCommThread
//
StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "Terminate Comm FIFO";
bretv = SetEvent(TerminationCTEvent);  	// destroy Comm thread - Comm_Thread & serial_event
assert(bretv);
ptrTIUCommThread = NULL;             	// FreeOnTerminate is set
CommThreadLED->Checked = false;	        // extinquish Comm Thread LED
CommThreadLED->LED->ColorOn = clLime;   // reset color
// -----------------------------------------------------------------------------
//
// 14. We are disconnected; enable port setup controls so user can change selections
//
StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "Enable Setup";
Setup->RFID_COM_Mode->Enabled = true;
Setup->Radio_COM_Mode->Enabled = true;
Setup->RFID_WiFi_Mode->Enabled = true;
Setup->WTIU_WiFi_Mode->Enabled = true;
//Setup->TIUIP->Enabled = Setup->Radio_WiFi_Mode->Checked || Setup->WTIU_WiFi_Mode->Checked;
//Setup->TIUPort->Enabled = Setup->Radio_WiFi_Mode->Checked || Setup->WTIU_WiFi_Mode->Checked;
//Setup->RFIDIP->Enabled = Setup->RFID_WiFi_Mode->Checked;
//Setup->RFIDPort->Enabled = Setup->RFID_WiFi_Mode->Checked;
Setup->RadioCOMPortNum->Enabled = Setup->Radio_COM_Mode->Checked;
Setup->RFIDCOMPortNum->Enabled = Setup->RFID_COM_Mode->Checked;
Setup->WTIUListBox->Enabled = true;
Setup->BaudRateSel->Enabled = Setup->Radio_COM_Mode->Checked;
Setup->ParitySel->Enabled = Setup->Radio_COM_Mode->Checked;
Setup->BitsSel->Enabled = Setup->Radio_COM_Mode->Checked;
Setup->StopSel->Enabled = Setup->Radio_COM_Mode->Checked;
// disable BUttons and Switches
Send_ASCII_TIU->Enabled = false;
Send_Hex_TIU->Enabled = false;
//RTSSwitch->Enabled = false;
//DTRSwitch->Enabled = false;
CTS->Checked = false;
DSR->Checked = false;
CD->Checked = false;
Ring->Checked = false;
STOPem->Enabled = false;
StartedConsists->Value = 0;
StartupButton->Enabled = false;
QuickButton->Enabled = false;
Var1ModeButton->Enabled = false;
Var2ModeButton->Enabled = false;
ShutdownButton->Enabled = false;
ReadEnginesButton->Enabled = false;
Prog_Control_Button->Enabled = true;
// disable Accessories/Switches Button
// AIUButton->Enabled = false;
// disable Stop All Button
StopAllButton->Enabled = false;
// disable LashUp buttons
LashUp->LashUpStartUpButton->Enabled = false;
LashUp->LashUpShutdownButton->Enabled = false;
// DTR not dropped in TIU Emulator mode bacause we might reset the Arduino
//if ((Setup->DropDTRonIdle->Checked  && !TIUEMULATORMODE) || Setup->DropRTSonIdle->Checked) {
//    IdleTimer->Enabled = false;		// turn off DTR drop timer
//	}

DisconnectButton->Enabled = false;
DisconnectRequest = false;			// clear [Disconnect] button flag
ConnectButton->Enabled = true;		// on success, enable connect button
ConnectButton->Caption = "&Connect";

if (SuperTIU) SuperTIU->SuperPanel->Enabled = DisconnectButton->Enabled;
if (ActiveTIU) ActiveTIU->ActivePanel->Enabled = ConnectButton->Enabled;

StatusBar->Panels->Items[TIU_PORT_STATUS]->Text = "Comm Disconnected";
// enable [Exit] Button escape
ExitButton->Enabled = true;
if (Setup->WTIU_WiFi_Mode->Checked) {
    Caption = MainForm->SavedCaption + " : TIU(s) Disconnected";   // put the TIU count in the Caption
    }
if (DEBUGGING) DB->Debug_Print("Disconnect Button: Disconnected");
return;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::Send_ASCII_TIUClick(TObject *Sender)
{
// Send to TIU Port
// Debug only
LOCKEM()
//
// Take the String in Encoded_CommandB and send to the TIU port
// it will send any hex bytes, not just 5,6,9 & A & 4D

if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
    return;
    }


if (TIULog->Checked) {
    // display it in the TIU buffer
    TIUBuffer->SelAttributes->Color = clBlue;
    TIUBuffer->SelStart = TIUBuffer->GetTextLen();
    TIUBuffer->SelText = Encoded_CommandA->Text + TEXT("\r\n"); // CR LF
    TIULineCount->Caption = String(TIULineCount->Caption.ToInt() + 1);
    ScrollEnd(TIUBuffer);    	// remain at end of TIU Buffer
    }
// Send it
// Does not record or handle errors
(void) ptrTIUCommThread->SendHexStrStr_FIFO(TIU_PORT, Encoded_CommandA->Text, Encoded_CommandA->Text.Length()/2);
}
//---------------------------------------------------------------------------

void  __fastcall TMainForm::Send_ASCII_RFIDClick(TObject *Sender)
{
LOCKEM()
// Send to RFID Port
// Debug only
//
// Take the String in Encoded_CommandB and send to the RFID port
// it will send any hex bytes, not just 5,6,9 & A & 4D

if (!IsRFIDConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
    return;
    }

if (RFIDLog->Checked) {
    // display it in the RFID buffer
    RFIDBuffer->SelAttributes->Color = clGreen;
    RFIDBuffer->SelStart = RFIDBuffer->GetTextLen();
    RFIDBuffer->SelText = Encoded_CommandB->Text + TEXT("\r\n");    // CR LF
    ScrollEnd(RFIDBuffer);    	// remain at end of Buffer
    }

// Send it and don't check for errors and don't wait for response
// Does not record or handle errors
(void) ptrTIUCommThread->SendHexStrStr_FIFO(RFID_PORT, Encoded_CommandB->Text, Encoded_CommandB->Text.Length()/2);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::Send_Hex_TIUClick(TObject *Sender)
{
LOCKEM();
// Debug only
//
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
    return;
    }

String CommandA = CleanUpCommand(Encoded_CommandA->Text);	// unlike previous function, this one cleans the command string
if (DEBUGGING) DB->Debug_Print("Send_Hex_AClick: " + CommandA);

// don't check for errors and don't wait for response
// Does not record or handle errors
(void) ptrTIUCommThread->SendHexStrStr_FIFO(TIU_PORT, CommandA, CommandA.Length()/2);
// bump sequence number
H_Seq_Count = H_Seq_Count +1;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::Send_Hex_RFIDClick(TObject *Sender)
{
LOCKEM();
// Debug only
//
if (!IsRFIDConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
    return;
    }

String CommandB = CleanUpCommand(Encoded_CommandB->Text);	// unlike previous function, this one cleans the command string
if (DEBUGGING) DB->Debug_Print("Send_Hex_BClick: " + CommandB);

// don't check for errors and don't wait for response
// Does not record or handle errors
(void) ptrTIUCommThread->SendHexStrStr_FIFO(RFID_PORT, CommandB, CommandB.Length()/2);
// bump sequence number
H_Seq_Count = H_Seq_Count +1;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::WritetoDiskClick(TObject *Sender)
{
// Write a RichEdit Control to the disk
// Note: started to edit to change to use the TStrings object Lines
// but the TStrings loses all of the RichEdit Colors and Fonts
//
String SourceBuffer;
TRichEdit * SelectedBuffer;
const TButton * ButtonUsed = (TButton *)Sender;

// setup pointer to buffer being saved
if (ButtonUsed->Name.Pos("RFID")) {
	SelectedBuffer = RFIDBuffer;
    SourceBuffer = "_RFID_";
    }
if (ButtonUsed->Name.Pos("TIU")) {
	SelectedBuffer = TIUBuffer;
    SourceBuffer = "_TIU_";
    }
if (ButtonUsed->Name.Pos("CRC")) {
	SelectedBuffer = CRCBuffer;
    SourceBuffer = "_CRC_";
    }

assert(SelectedBuffer);
if( SelectedBuffer->Lines->Count == 0) return;	// buffer is empty

SaveDialog1->InitialDir = WorkingDir;

String NameOfFile;
if (PlainTextFile->Checked) {  // Plain text - make .txt file
	SaveDialog1->DefaultExt = String("txt");
	SaveDialog1->Filter = "Text files (*.txt)|*.txt";
    SaveDialog1->FileName = FileNamePrefix->Text + SourceBuffer + "_" + FileNameSuffix->Text + ".txt";
    } else {					// Rich text - make .rtf file
	SaveDialog1->DefaultExt = String("rtf");
	SaveDialog1->Filter = "Rich text files (*.rtf)|*.rtf";
    SaveDialog1->FileName = FileNamePrefix->Text + SourceBuffer + "_"+ FileNameSuffix->Text + ".rtf";
    }
SaveDialog1->Title = "Save File";
SaveDialog1->Options = SaveDialog1->Options >> ofOverwritePrompt << ofEnableSizing;	// RTC will handle this case

if(SaveDialog1->Execute()) {
	NameOfFile = SaveDialog1->FileName;
    // Change working directory to whatever was selected
    WorkingDir = ExtractFilePath(NameOfFile);
    // if file exists, confirm first
    if (FileExists(NameOfFile)) {
        // ask for confirmation
        int result = Application->MessageBox(TEXT("File Exists - Do you want to overwrite?"), Caption.c_str(), MB_YESNO | MB_ICONQUESTION);
    	// if No, exit
    	if (result  == IDNO) {
            return;
        	}
    	}
    // At this point, it's either a new file or the user has indicated overwrite
    // text only file if PlainText is checked
    bool savPlainText = SelectedBuffer->PlainText;
    SelectedBuffer->PlainText = PlainTextFile->Checked;   // if Checked set PlainText to true
	SelectedBuffer->Lines->SaveToFile(NameOfFile);
	if (DEBUGGINGX(1)) DB->Debug_Print("WritetoDisk: " + NameOfFile + " - " + String(SelectedBuffer->Lines->Count) + " Lines");
    SelectedBuffer->PlainText = savPlainText;
	return;	// done
	} else {
    return;	// user canceled
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ReadfromDiskClick(TObject *Sender)
{
String SourceBuffer;
TRichEdit * SelectedBuffer;
const TButton * ButtonUsed = (TButton *)Sender;

// setup pointer to buffer being read into
if (ButtonUsed->Name.Pos("RFID")) {
	SelectedBuffer = RFIDBuffer;
    SourceBuffer = "_RFID_";
    }
if (ButtonUsed->Name.Pos("TIU")) {
	SelectedBuffer = TIUBuffer;
    SourceBuffer = "_TIU_";
    }
if (ButtonUsed->Name.Pos("CRC")) {
	SelectedBuffer = CRCBuffer;
    SourceBuffer = "_CRC_";
    }

OpenDialog1->InitialDir = WorkingDir;

String NameOfFile;
if (PlainTextFile->Checked) {  // Plain text - make .txt file
	OpenDialog1->DefaultExt = String("txt");
	OpenDialog1->Filter = "Text files (*.txt)|*.txt";
	OpenDialog1->FileName = FileNamePrefix->Text + SourceBuffer + "_"+ FileNameSuffix->Text + ".txt";
    } else {
	OpenDialog1->DefaultExt = String("rtf");
	OpenDialog1->Filter = "Rich Text files (*.rtf)|*.rtf";
	OpenDialog1->FileName = FileNamePrefix->Text + SourceBuffer + "_"+ FileNameSuffix->Text + ".rtf";
    }
OpenDialog1->Title = "Open File";
OpenDialog1->Options = OpenDialog1->Options << ofEnableSizing;	// Allow Resizing

if(OpenDialog1->Execute()) {
	SelectedBuffer->Clear();
	NameOfFile = OpenDialog1->FileName;
    // Change working directory to whatever was selected
    WorkingDir = ExtractFilePath(NameOfFile);
    // text only file
    bool savPlainText = SelectedBuffer->PlainText;
    SelectedBuffer->PlainText = PlainTextFile->Checked;   // if Checked set PlainText to true
    SelectedBuffer->Lines->LoadFromFile(NameOfFile);
    SelectedBuffer->PlainText = savPlainText;
    if (DEBUGGING) DB->Debug_Print("ReadFromDisk: " + NameOfFile);
	} else {
    return;	// user canceled
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::STOPemClick(TObject *Sender)
{
bStop = true;					// set flag, then
STOPem->Enabled = false;		// disable button
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ResetTIUCCButtonClick(TObject *Sender)
{
// Reset all TIU Port Counters
CTS_Count = DSR_Count = CD_Count = 0;
CTS_Counter->Text = String(CTS_Count);
DSR_Counter->Text = String(DSR_Count);
CD_Counter->Text = String(CD_Count);
// TIU related counts
TIU_Char_Count->Text = "0";
TIU_Total_Byte_Count->ValueAsInt = 0;
TIULineCount->Caption = "0";
// Communications related counts
PacketsSent->ValueAsInt = 0;
sendDataBusy->ValueAsInt = 0;
Retries->ValueAsInt = 0;
NAKShort->ValueAsInt = 0;	// NAK counter #1
NAKLen->ValueAsInt = 0;		// NAK counter #2
NAK0T->ValueAsInt = 0;		// NAK counter #3
NAKCRC->ValueAsInt = 0;		// NAK counter #4
ReentrantErrors->ValueAsInt = 0;
CaughtErrors->ValueAsInt = 0;
EngNotFound->ValueAsInt = 0;
// FIFO's and Dispatch List
Dispatched_Cmd_Count->ValueAsInt = 0;
FIFO_Count->ValueAsInt = 0;
DL_FIFO_Total->ValueAsInt = 0;
DL_FIFO_Count->ValueAsInt = 0;
// Tag related Counters
Tags->ValueAsInt = 0;  // total number of packets received from the detectors (includes startup packets)
// reset Tag related counters - manually only - never reset as a side effect
NumDetectors->ValueAsInt = 0;
NumReaders->ValueAsInt = 0;
WiFiTO->ValueAsInt = 0;
CKSum->ValueAsInt = 0;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ResetRFIDCCButtonClick(TObject *Sender)
{
// Clear RFID Byte Count
//iRFIDbufCount = 0;
RFID_Char_Count->Text = "0";
Tags->ValueAsInt = 0;  // total number of packets received from the detectors (includes startup packets)
}
//---------------------------------------------------------------------------
unsigned char __fastcall HexStringtoInt(unsigned char * Str)
{
// not used
assert(false);
// converts a 2 Hexidecimal character string to binary
static const char* const lut = "0123456789ABCDEF";
// Convert Hex character string to Hex
char a = Str[0];
const char* p = std::lower_bound(lut, lut + 16, a);
if (*p != a) {
   	if (DEBUGGING) DB->Debug_Print("HexStringtoInt: Error: A Not a Hex Digit : " + String((char *)Str), clRed);
    return false;
    }

char b = Str[1];
const char* q = std::lower_bound(lut, lut + 16, b);
if (*q != b) {
	if (DEBUGGING) DB->Debug_Print("HexStringtoInt: Error: A Not a Hex Digit : " + String((char *)Str), clRed);
	return false;
    }
return (((p - lut) << 4) | (q - lut));
}
//---------------------------------------------------------------------------

String Convert16B8BNumber(String W)
{
// Convert 4 digit hex encoded 16 bits string to 8 bit Morton Numbers in binary
// (Call bitswapRX() after calling this function)
/*
55	0000 0		56	0001 1		59	0010 2		5A  0011 3
65	0100 4		66	0101 5		69	0110 6		6A	0111 7
95	1000 8		96	1001 9		99	1010 10		9A	1011 11
A5	1100 12		A6	1101 13		A9	1110 14		AA	1111 15

This is an RLL(0,1) encoding
*/
	const static String Encoded[] = {"55",  "56",  "59",  "5A",  "65",  "66",  "69",  "6A",  "95",  "96",  "99",  "9A",  "A5",  "A6",  "A9",  "AA"};
	const static String Binary[] = {"0000","0001","0010","0011","0100","0101","0110","0111","1000","1001","1010","1011","1100","1101","1110","1111"};
    int Digit1, Digit2;
	String Dig1 = W.SubString(1,2);
    Dig1 = Dig1.UpperCase();
    for (Digit1 = 0 ; Digit1 < 16 ; Digit1++) {
        if (Dig1 == Encoded[Digit1]) {
        	break;
            }
    	}
	String Dig2 = W.SubString(3,2);
    Dig2 = Dig2.UpperCase();
    for (Digit2 = 0 ; Digit2 < 16 ; Digit2++) {
        if (Dig2 == Encoded[Digit2]) {
        	break;
            }
    	}
if (Digit1 == 16 || Digit2 == 16)
    return "00";
    else
    return Binary[Digit1] + Binary[Digit2];

}
//---------------------------------------------------------------------------

String Convert8B16BNumber(String W)
{
// convert 8 bit Morton Nnumbers in binary to 4 digit hex encoded 16 bit String
// (call bitswapTX() before calling this function)
// NO VCL calls in this function
//
/* Description by ChatGPT
Function Description

Convert8B16BNumber converts an 8-bit binary string (representing a Morton number) into a 16-bit hexadecimal string using a specific encoding table.

Step by step:

1. The input W is assumed to be an 8-character binary string.

The function expects that bitswapTX() has already been applied if needed.

2. It splits W into two 4-bit halves:

Dig1 = first 4 bits (W[0..3])

Dig2 = last 4 bits (W[4..7])

3. Each 4-bit half is compared against a binary-to-encoded table:

const static String Binary[] = {"0000","0001", ..., "1111"};
const static String Encoded[] = {"55","56","59","5A",..., "AA"};


The function finds the index of each 4-bit half in Binary[].

That index is then used to look up the corresponding encoded hexadecimal string in Encoded[].

4. If either 4-bit half is invalid (not found in Binary[]), it returns "0000".

5. Otherwise, it concatenates the two 2-character encoded strings to form a 4-character hex string (16 bits) and returns it.

*/
	const static String Encoded[] = {"55",  "56",  "59",  "5A",  "65",  "66",  "69",  "6A",  "95",  "96",  "99",  "9A",  "A5",  "A6",  "A9",  "AA"};
	const static String Binary[] = {"0000","0001","0010","0011","0100","0101","0110","0111","1000","1001","1010","1011","1100","1101","1110","1111"};
    int Digit1, Digit2;
	String Dig1 = W.SubString(1,4);
    Dig1 = Dig1.UpperCase();
    for (Digit1 = 0 ; Digit1 < 16 ; Digit1++) {
        if (Dig1 == Binary[Digit1]) {
        	break;
            }
    	}
	String Dig2 = W.SubString(5,4);
    Dig2 = Dig2.UpperCase();
    for (Digit2 = 0 ; Digit2 < 16 ; Digit2++) {
        if (Dig2 == Binary[Digit2]) {
        	break;
            }
    	}
if (Digit1 == 16 || Digit2 == 16)
	return "0000";
    else
    return Encoded[Digit1] + Encoded[Digit2];
}
//---------------------------------------------------------------------------

// a version of Convert8B16BNumber(String) written by ChatGPT
/*
It uses direct table lookup via index calculation instead of loops:

1. No loops over arrays — binary substrings are converted to index using bit operations.
2. Handles invalid characters gracefully (-1 ? "0000").
3. Easier to read and maintain.
*/

// Convert 4-bit binary substring to integer index
int binToIndex(String s) {
    int val = 0;
    for (int i = 0; i < 4; i++) {
        if (s[i] == '1') val |= (1 << (3 - i));
        else if (s[i] != '0') return -1; // invalid character
    }
    return val;
}

String Convert8B16BNumberChatGPT(String W)
{
    // convert 8-bit Morton numbers in binary to 4-digit hex encoded 16-bit string
    // W must already be bitswapped if needed

    const static String Encoded[] = {"55","56","59","5A","65","66","69","6A",
                                     "95","96","99","9A","A5","A6","A9","AA"};

    if (W.Length() != 8) return "0000";

    int Digit1 = binToIndex(W.SubString(1,4));
    int Digit2 = binToIndex(W.SubString(5,4));

    if (Digit1 < 0 || Digit2 < 0) return "0000";

    return Encoded[Digit1] + Encoded[Digit2];
}
//---------------------------------------------------------------------------

// An improved version written by ChatGPT
/*
 Here’s a single, efficient function that combines bitswapping and 8 to16-bit encoding in one step,
avoiding intermediate strings

How it works

1. No intermediate strings — we calculate the final encoded indices directly from the input.
2. Bitswap: odd bits come first, even bits second (like your bitswapTX).
3. Binary to Index: each 4-bit chunk is converted to an integer (0–15) using bit operations.
4. Lookup table: Encoded 16-bit hex string is produced.
5. Invalid input returns "0000".

*/

// Compute the bitswap index directly
int binToIndex (const String s, const int order[4]) {
    int val = 0;
    for (int i = 0; i < 4; i++) {
        char c = s[order[i]];
        if (c == '1') val |= (1 << (3 - i));
        else if (c != '0') return -1; // invalid character
    }
    return val;
}

String BitswapAndConvert8B16B(String W)
{
    // Precondition: W is 8-character binary string
    if (W.Length() != 8) return "0000";

    const static String Encoded[] = {"55","56","59","5A","65","66","69","6A",
                                     "95","96","99","9A","A5","A6","A9","AA"};

    // Odd-even bits swap order: odd positions first (0-based indexing)
    const int order1[4] = {0,2,4,6}; // first 4 bits after swap
    const int order2[4] = {1,3,5,7}; // last 4 bits after swap

    int Digit1 = binToIndex(W, order1);
    int Digit2 = binToIndex(W, order2);

    if (Digit1 < 0 || Digit2 < 0) return "0000";

    return Encoded[Digit1] + Encoded[Digit2];
}
//---------------------------------------------------------------------------

String bitswapRX(String S)
{
//
// Do a bit swap on a received word
// Convert from Morton Numbers to Binary
//
String bitswap =
    S.SubString(1,1) +
    S.SubString(5,1) +
    S.SubString(2,1) +
    S.SubString(6,1) +
    S.SubString(3,1) +
    S.SubString(7,1) +
    S.SubString(4,1) +
    S.SubString(8,1);
return bitswap;
}

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

String bitswapTX(String S)
{
//
// Do a bit swap on a word to be transmitted
// Convert from Binary into Morton Numbers
//
String bitswap =
    S.SubString(1,1) +
    S.SubString(3,1) +
    S.SubString(5,1) +
    S.SubString(7,1) +
    S.SubString(2,1) +
    S.SubString(4,1) +
    S.SubString(6,1) +
    S.SubString(8,1);
return bitswap;
}
//---------------------------------------------------------------------------

unsigned char bintodec8(String W)
{
// 8 bit binary String to 8 bit unsigned char
unsigned char result = 0;
result += (W.SubString(1,1) == "1") ? (unsigned char)128 : (unsigned char)0;
result += (W.SubString(2,1) == "1") ? (unsigned char)64 : (unsigned char)0;
result += (W.SubString(3,1) == "1") ? (unsigned char)32 : (unsigned char)0;
result += (W.SubString(4,1) == "1") ? (unsigned char)16 : (unsigned char)0;
result += (W.SubString(5,1) == "1") ? (unsigned char)8 : (unsigned char)0;
result += (W.SubString(6,1) == "1") ? (unsigned char)4 : (unsigned char)0;
result += (W.SubString(7,1) == "1") ? (unsigned char)2 : (unsigned char)0;
result += (W.SubString(8,1) == "1") ? (unsigned char)1 : (unsigned char)0;
return result;
}
//---------------------------------------------------------------------------

String dectobin8(int num)  // 8 bit to 8 bit binary String
{
// 8 bit to 8 bit binary String
const static int steps[] = {128, 64, 32, 16, 8, 4, 2, 1};
String binary;

num = num & 0x00FF;	// this routine only handles unsigned char (0-255)

if(num > 0)	{
	int stepLen = sizeof(steps) / sizeof(int);
	for(int x = 0; x < stepLen; x++) {
		if(num >= steps[x]) {
			num -= steps[x];
			binary += "1";
			} else
			binary += "0";
		}
	}
	else {
	binary = "00000000";
    }
return binary;
}
//---------------------------------------------------------------------------

// a version of dectobin8(int) written by ChatGPT
/*
How it works:

1. num >> i shifts the number i bits to the right, bringing the bit we want into the least significant position.

2. & 1 extracts that bit (0 or 1).

3. The ternary operator appends "1" or "0" to the output string.

4. Looping i from 7 to 0 ensures the most significant bit comes first, matching your original function.
*/

String dectobin8ChatGPT(int num) {
    num &= 0xFF; // ensure only 8 bits
    String binary = "";
    
    for (int i = 7; i >= 0; i--) {
        binary += ((num >> i) & 1) ? "1" : "0";
    }
    
    return binary;
}
//---------------------------------------------------------------------------
void __fastcall TMainForm::PasteIntoRFIDBufferClick(TObject *Sender)
{
// Remote Buffer popup menu item
// Text is Green
RFIDBuffer->SelAttributes->Color = clGreen;
RFIDBuffer->PasteFromClipboard();
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CopyIntoRFIDBufferClick(TObject *Sender)
{
// Remote Buffer popup menu item
RFIDBuffer->CopyToClipboard();
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::SelAllRFIDBufferClick(TObject *Sender)
{
// Remote Buffer popup menu item
RFIDBuffer->SelectAll();
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::SendHexFromRemoteBufferClick(TObject *Sender)
{
// Remote buffer popup menu
String CommandA = RFIDBuffer->SelText;
if (!CommandA.IsEmpty()) {
	CommandA = CleanUpCommand(CommandA);
    if (ValidCommand(CommandA)) {
    	// Does not record or handle errors
		(void) ptrTIUCommThread->SendHexStrStr_FIFO(TIU_PORT, CommandA, CommandA.Length()/2);
        }
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ExpandRemoteCommandClick(TObject *Sender)
{
// Remote Buffer popup menu item
// insert spaces into command line

// If there is a selected text active, then expand that selected text
// Expand the selection from the Main Buffer
TPoint CaretPos = RFIDBuffer->CaretPos;
const String OriginalCmd = RFIDBuffer->Lines->Strings[CaretPos.y];
//
String MyCmd = RFIDBuffer->SelText;
if (MyCmd.IsEmpty()) {
	// otherwise, if the cursor is on a line,
    // then expand the selection on that line
    // get MyCmd from the cursor line
    MyCmd = CleanUpCommand(OriginalCmd);
    } else {
    // Selection Active, so use the selection
    MyCmd = CleanUpCommand(MyCmd);
    }
//
CRCBuffer->SelAttributes->Color = RFIDBuffer->SelAttributes->Color;	// Copy with same color
CRCBuffer->Lines->Append(OriginalCmd);
ScrollEnd(CRCBuffer);   // remain at end of buffer

MyCmd = ExpandCommand(MyCmd); // expand and print the command in MyCmd
if (!MyCmd.IsEmpty()) {
	RFIDBuffer->SelText = MyCmd;
    CRCBuffer->SelAttributes->Color = clBlue;
	CRCBuffer->Lines->Append(MyCmd);
    ScrollEnd(CRCBuffer);   // remain at end of buffer;
	}
}
//---------------------------------------------------------------------------

bool __fastcall TMainForm::ValidCommand(String Command)
{
if (!Command.IsEmpty()) {
	if (DEBUGGING) DB->Debug_Print("ValidCommand: " + Command);
   	Command = CleanUpCommand(Command);
    // must be a command of correct length
    const int CommandLen = Command.Length();
    if (CommandLen < 30) {		// minimum command lenght Sync + Engine + command + Seq + TIU/Rem + CRCH + CRCL + EOT
        if (DEBUGGING) DB->Debug_Print("ValidCommand: Error: Command less than 30 encoded characters", clRed);
        return false;
        }
    // Command must start with 5555
    if (Command.SubString(1,4) != "5555") {
    	if (DEBUGGING) DB->Debug_Print("ValidCommand: Error: Command does not start with 5555", clRed);
    	return false;
        }
    // Command must end with 4D, last character
    if (Command.SubString(CommandLen-1,2) != "4D") {
        if (DEBUGGING) DB->Debug_Print("ValidCommand: Error: Command does not end with 4D", clRed);
        return false;
    	}
    } else {
    // command is empty
    if (DEBUGGING) DB->Debug_Print("ValidCommand: Error: Command is empty", clRed);
    return false;
    }
return true;
}
//---------------------------------------------------------------------------

String __fastcall TMainForm::ExpandCommand(String Command)
{
if (!Command.IsEmpty()) {
	//if (DEBUGGING) DB->Debug_Print("Expand Command: " + Command);
   	Command = CleanUpCommand(Command);
    //if (ValidCommand(Command)) {
		// insert spaces
        int CommandLen = Command.Length();
        int idxec2 = 5;
        for (int idxec = 5 ; idxec < CommandLen ; idxec += 4) {
        	Command.Insert(" ", idxec2);
            idxec2 += 5;
        	}
        // adjust spaces to line up the command bits
        if (CommandLen == 30) {
            Command.Insert("                    ", 16);
        	} else {
            if (CommandLen == 34) {
                Command.Insert("               ", 21);
            	} else {
                if (CommandLen == 38) {
                    Command.Insert("          ", 26);
                	} else {
                	if (CommandLen == 42) {
                        Command.Insert("     ", 31);
                		}
                    }
                }
            }
        return Command;
        // }
    }
return "";
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ExpandRemoteBufferClick(TObject *Sender)
{
// Remote Buffer popup menu item

// expand Commands in entire buffer
// insert spaces into command line
if (DEBUGGING) STOPem->Enabled = true;
bStop = false;

for (int idxeb = 0; idxeb < RFIDBuffer->Lines->Count; idxeb++) {
	if (bStop) {
       	break;
        }
    String FullLine = RFIDBuffer->Lines->Strings[idxeb];
    String Command = CleanUpCommand(RFIDBuffer->Lines->Strings[idxeb]);
    // find the command on the line if there is one
    int Endpos = FullLine.Pos("4D") + 1;
    if (Endpos == 1) {
    	continue; // no command on the line
        }
    Command = Command.SubString(1, Endpos);
    int CommandLen = Command.Length();

	if (!Command.IsEmpty()) {
		 String NewCommand = ExpandCommand(Command);
		 if (!NewCommand.IsEmpty()) {
         	// replace only the part of the Line that contains the command
            // to retain any comments
    	 	FullLine.Delete(1, CommandLen);
            FullLine.Insert(NewCommand, 1);
            RFIDBuffer->Lines->Strings[idxeb] = FullLine;
		    CRCBuffer->SelAttributes->Color = clBlue;
            CRCBuffer->Lines->Append(FullLine);
            ScrollEnd(CRCBuffer);   // remain at end of buffer
        	}
	    }
	Application->ProcessMessages();
    }
STOPem->Enabled = false;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::Encode_TIUClick(TObject *Sender)
{
// encode a command
if (D_EngNo->Text == "-") return;
Encoded_CommandA->Clear();

// Sync Byte
Encoded_CommandA->Text = Encoded_CommandA->Text + "5555";

// Engine Number
Encoded_CommandA->Text = Encoded_CommandA->Text + Convert8B16BNumber(bitswapTX(dectobin8(D_EngNo->Text.ToInt())));

// Command
Encoded_CommandA->Text = Encoded_CommandA->Text + Convert8B16BNumber(bitswapTX(dectobin8(D_Command->Text.ToInt())));

//  Data Bytes
if (D_Data1->Text  != "-" ) {
	Encoded_CommandA->Text = Encoded_CommandA->Text + Convert8B16BNumber(bitswapTX(dectobin8(D_Data1->Text.ToInt())));
    }
if (D_Data2->Text  != "-" ) {
	Encoded_CommandA->Text = Encoded_CommandA->Text + Convert8B16BNumber(bitswapTX(dectobin8(D_Data2->Text.ToInt())));
    }
if (D_Data3->Text  != "-" ){
	Encoded_CommandA->Text = Encoded_CommandA->Text + Convert8B16BNumber(bitswapTX(dectobin8(D_Data3->Text.ToInt())));
    }
if (D_Data4->Text  != "-" ) {
	Encoded_CommandA->Text = Encoded_CommandA->Text + Convert8B16BNumber(bitswapTX(dectobin8(D_Data4->Text.ToInt())));
    }
Encoded_CommandA->Text = Encoded_CommandA->Text + Convert8B16BNumber(bitswapTX(dectobin8(D_Seq_Count->Text.ToInt())));

// TIU_Remote Byte
Encoded_CommandA->Text = Encoded_CommandA->Text + Convert8B16BNumber(bitswapTX(dectobin8(D_TIU_Remote->Text.ToInt())));

// CRC
Encoded_CommandA->Text = Encoded_CommandA->Text + Convert8B16BNumber(bitswapTX(dectobin8(D_CRC_LSB->Text.ToInt())));
Encoded_CommandA->Text = Encoded_CommandA->Text + Convert8B16BNumber(bitswapTX(dectobin8(D_CRC_MSB->Text.ToInt())));

// EOT
Encoded_CommandA->Text = Encoded_CommandA->Text + "4D";
//if (DEBUGGING) DB->Debug_Print("Encode_AClick: " + Encoded_CommandA->Text);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::Encode_RemoteClick(TObject *Sender)
{
// encode a command
if (D_EngNo->Text == "-") return;
Encoded_CommandB->Clear();

// Sync Byte
Encoded_CommandB->Text = Encoded_CommandB->Text + "5555";

// Engine Number
Encoded_CommandB->Text = Encoded_CommandB->Text + Convert8B16BNumber(bitswapTX(dectobin8(D_EngNo->Text.ToInt())));

// Command
Encoded_CommandB->Text = Encoded_CommandB->Text + Convert8B16BNumber(bitswapTX(dectobin8(D_Command->Text.ToInt())));

// Data Bytes
if (D_Data1->Text  != "-" ) {
	Encoded_CommandB->Text = Encoded_CommandB->Text + Convert8B16BNumber(bitswapTX(dectobin8(D_Data1->Text.ToInt())));
    }
if (D_Data2->Text  != "-" ) {
	Encoded_CommandB->Text = Encoded_CommandB->Text + Convert8B16BNumber(bitswapTX(dectobin8(D_Data2->Text.ToInt())));
    }
if (D_Data3->Text  != "-" ){
	Encoded_CommandB->Text = Encoded_CommandB->Text + Convert8B16BNumber(bitswapTX(dectobin8(D_Data3->Text.ToInt())));
    }
if (D_Data4->Text  != "-" ) {
	Encoded_CommandB->Text = Encoded_CommandB->Text + Convert8B16BNumber(bitswapTX(dectobin8(D_Data4->Text.ToInt())));
    }
Encoded_CommandB->Text = Encoded_CommandB->Text + Convert8B16BNumber(bitswapTX(dectobin8(D_Seq_Count->Text.ToInt())));

// TIU_Remote Byte
Encoded_CommandB->Text = Encoded_CommandB->Text + Convert8B16BNumber(bitswapTX(dectobin8(D_TIU_Remote->Text.ToInt())));

// CRC
Encoded_CommandB->Text = Encoded_CommandB->Text + Convert8B16BNumber(bitswapTX(dectobin8(D_CRC_LSB->Text.ToInt())));
Encoded_CommandB->Text = Encoded_CommandB->Text + Convert8B16BNumber(bitswapTX(dectobin8(D_CRC_MSB->Text.ToInt())));

// EOT
Encoded_CommandB->Text = Encoded_CommandB->Text + "4D";
//if (DEBUGGING) DB->Debug_Print("EncodeBClick: " + Encoded_CommandB->Text);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::DecodeCommandBufferClick(TObject *Sender)
{
// Remote Buffer and TIU Buffer popup menu item

TRichEdit * SelectedBuffer;
TPopupMenu * MenuUsed;
MenuUsed = (TPopupMenu *)Sender;
if (MenuUsed->Name.Pos("RFID")) SelectedBuffer = RFIDBuffer;
if (MenuUsed->Name.Pos("TIU")) SelectedBuffer = TIUBuffer;

// Decode all commands in the Selected Buffer and write them to the CRC Buffer
if (DEBUGGING) STOPem->Enabled = true;
bStop = false;
//
const int count = SelectedBuffer->Lines->Count;

for (int idxdb = 0 ; idxdb < count ; idxdb++) {
   if (bStop) {
        break;
        }
	String Command = SelectedBuffer->Lines->Strings[idxdb];
    CRCBuffer->SelAttributes->Color = clBlue;
   	CRCBuffer->Lines->Append(Command);
    ScrollEnd(CRCBuffer);   // remain at end of buffer

    Command = CleanUpCommand(Command);
	if (ValidCommand(Command)) {
        DecodePrint(Command); // Decode and print the command found on the line
    	}
	Application->ProcessMessages();
	}
STOPem->Enabled = false;
}
//---------------------------------------------------------------------------

String __fastcall TMainForm::CleanUpCommand(String Command)
{
	////if (DEBUGGING) DB->Debug_Print("CleanUpCommand: " + Command);
	// remove spaces if any are there
	while (true) {
    	const int position = Command.Pos(" ");
    	if (position == 0) break;
    	// remove space
    	Command.Delete(position, 1);
		}
    // remove zeroes
    while (true) {
    	const int position = Command.Pos("0");
    	if (position == 0) break;
    	// remove zero
    	Command.Delete(position, 1);
		}
    // make the string upper case
    Command = Command.UpperCase();
    // remove anything after the 4D if there is a 4D on the line
    const int Endpos = Command.Pos("4D") + 1;
    if (Endpos == 1) {
    	return Command; // no 4D on the line so this command will fail future checks
        }
    // remove everything after the 4D
    Command = Command.SubString(1, Endpos);
	return Command;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::PrintCommand(void)
{
	// insert decoded command into the RFIDBuffer
    TCHAR os[100];
    CRCBuffer->SelAttributes->Color = clBlue;
	CRCBuffer->Lines->Append("   Sync  Eng  Cmd    1    2    3    4  Seq  T/R  LSB  MSB");
    ScrollEnd(CRCBuffer);   // remain at end of buffer
    CRCBuffer->SelAttributes->Color = clBlue;
	CRCBuffer->Lines->Append("   ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----");
    ScrollEnd(CRCBuffer);   // remain at end of buffer
    _sntprintf(os, sizeof os, TEXT("      0%5d%5d%5.0d%5.0d%5.0d%5.0d%5d%5d%5d%5d"),	
            	D_EngNo->Text == "-" ? 0 : D_EngNo->Text.ToInt(),
                D_Command->Text == "-" ? 0 : D_Command->Text.ToInt(),
                D_Data1->Text == "-" ? 0 : D_Data1->Text.ToInt(),
                D_Data2->Text == "-" ? 0 : D_Data2->Text.ToInt(),
                D_Data3->Text == "-" ? 0 : D_Data3->Text.ToInt(),
                D_Data4->Text == "-" ? 0 : D_Data4->Text.ToInt(),
                D_Seq_Count->Text == "-" ? 0 : D_Seq_Count->Text.ToInt(),
                D_TIU_Remote->Text == "-" ? 0 : D_TIU_Remote->Text.ToInt(),
                D_CRC_LSB->Text == "-" ? 0 : D_CRC_LSB->Text.ToInt(),
                D_CRC_MSB->Text == "-" ? 0 : D_CRC_MSB->Text.ToInt()
                );
    CRCBuffer->SelAttributes->Color = clBlue;
    CRCBuffer->Lines->Append((String)os);
    ScrollEnd(CRCBuffer);   // remain at end of bufferfer
    _sntprintf(os, sizeof os, TEXT("   0x00 0x%2s 0x%2s %4s %4s %4s %4s 0x%2s 0x%2s 0x%2s 0x%2s"),
            	H_EngNo->Text.c_str(),
                H_Command->Text.c_str(),
				H_Data1->Text == "-" ? TEXT("    ") : (TEXT("0x") + H_Data1->Text).c_str(),
				H_Data2->Text == "-" ? TEXT("    ") : (TEXT("0x") + H_Data2->Text).c_str(),
				H_Data3->Text == "-" ? TEXT("    ") : (TEXT("0x") + H_Data3->Text).c_str(),
				H_Data4->Text == "-" ? TEXT("    ") : (TEXT("0x") + H_Data4->Text).c_str(),
                H_Seq_Count->Text.c_str(),
                H_TIU_Remote->Text.c_str(),
                H_CRC_LSB->Text.c_str(),
                H_CRC_MSB->Text.c_str()
                );
    CRCBuffer->SelAttributes->Color = clBlue;
    CRCBuffer->Lines->Append((String)os);
    ScrollEnd(CRCBuffer);   // remain at end of buffer
    // print command byte in ASCII
    String A_Command;
    if (H_Command->Text != "-") {
          A_Command = (char)D_Command->Text.ToInt();  // Convert to ASCII Character	
    	  A_Command = "'" + A_Command + "'";
          }
    // print data bytes in ASCII
    String A_Data1, A_Data2, A_Data3, A_Data4;
    if (H_Data1->Text != "-") {
          A_Data1 = (char)D_Data1->Text.ToInt();  // Convert to ASCII Character 	
    	  A_Data1 = "'" + A_Data1 + "'";
          }
    if (H_Data2->Text != "-") {
          A_Data2 = (char)D_Data2->Text.ToInt();  // Convert to ASCII Character	
    	  A_Data2 = "'" + A_Data2 + "'";
          }
    if (H_Data3->Text != "-") {
          A_Data3 = (char)D_Data3->Text.ToInt();  // Convert to ASCII Character	
    	  A_Data3 = "'" + A_Data3 + "'";
          }
    if (H_Data4->Text != "-") {
          A_Data4 = (char)D_Data4->Text.ToInt();  // Convert to ASCII Character	
    	  A_Data4 = "'" + A_Data4 + "'";
          }
    _sntprintf(os, sizeof os, TEXT("             %4s %4s %4s %4s %4s"),
    			H_Command->Text == "-" ? TEXT("    ") : A_Command.c_str(),
                H_Data1->Text == "-" ? TEXT("    ") : A_Data1.c_str(),
                H_Data2->Text == "-" ? TEXT("    ") : A_Data2.c_str(),
                H_Data3->Text == "-" ? TEXT("    ") : A_Data3.c_str(),
                H_Data4->Text == "-" ? TEXT("    ") : A_Data4.c_str()
                );
    CRCBuffer->SelAttributes->Color = clBlue;
    CRCBuffer->Lines->Append((String)os);
    ScrollEnd(CRCBuffer);   // remain at end of buffer
    }
//---------------------------------------------------------------------------

void __fastcall TMainForm::DecodePrint(String Command)
{
// Decode and print Command
if (DecodeMyCommand(Command)) {
	PrintCommand();
	}
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::DecodeCommandClick(TObject *Sender)
{
// Remote Buffer and TIU Buffer popup menu item

TRichEdit * SelectedBuffer;
TPopupMenu * MenuUsed;
MenuUsed = (TPopupMenu *)Sender;
if (MenuUsed->Name.Pos("RFID")) SelectedBuffer = RFIDBuffer;
if (MenuUsed->Name.Pos("TIU")) SelectedBuffer = TIUBuffer;

//If there is a selected text active, then decode that selected text
//Decode the selected command from the Main Buffer
TPoint CaretPos = SelectedBuffer->CaretPos;
String OriginalCmd = SelectedBuffer->Lines->Strings[CaretPos.y];
//
String MyCmd = SelectedBuffer->SelText;
if (MyCmd.IsEmpty()) {
	// otherwise, if the cursor is on a line with a valid command,
    // then decode the command on that line
    // get MyCmd from the cursor line
    MyCmd = CleanUpCommand(OriginalCmd);
	if (!ValidCommand(MyCmd)) {
    	return;
        }
    } else {
    // Selection Active, so use the selection
    MyCmd = CleanUpCommand(MyCmd);
    }
//
CRCBuffer->SelAttributes->Color = SelectedBuffer->SelAttributes->Color;	// Copy with same color
CRCBuffer->Lines->Append(OriginalCmd);
ScrollEnd(CRCBuffer);   // remain at end of buffer

DecodePrint(MyCmd); // Decode and print the command in MyCmd
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ClearDecode() {
// clear all of the decode fields - only the H_* field needs to be cleared
// Its event handler will clear the D_* and A_* fields
EngNo->Text = "-";
H_EngNo->Text = "-";

Command->Text = "-";
H_Command->Text = "-";

Data1->Text = "-";
H_Data1->Text = "-";

Data2->Text = "-";
H_Data2->Text = "-";

Data3->Text = "-";
H_Data3->Text = "-";

Data4->Text = "-";
H_Data4->Text = "-";

Seq_Count->Text = "-";
H_Seq_Count->Text = "-";

TIU_Remote->Text = "-";
H_TIU_Remote->Text = "-";

CRC_LSB->Text = "-";
H_CRC_LSB->Text = "-";

CRC_MSB->Text = "-";
H_CRC_MSB->Text = "-";

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

bool __fastcall TMainForm::DecodeMyCommand(String MyCmd) {
// Decoded a command into separate fields.
//
// Return true if successful
// return false if error
//
//if (DEBUGGING) DB->Debug_Print("DecodeMyCommand: " + MyCmd);

ClearDecode();	// Clear all fields

if (!MyCmd.IsEmpty()) {
   	// remove spaces if any are there
    MyCmd = CleanUpCommand(MyCmd);
    if (ValidCommand(MyCmd)) {
        int CommandLen = MyCmd.Length();
    	//
    	EngNo->Text = MyCmd.SubString(5, 4);
		String C_EngNo = Convert16B8BNumber(EngNo->Text);
		String I_EngNo = bintodec8(bitswapRX(C_EngNo));
    	H_EngNo->Text = IntToHex(I_EngNo.ToInt(), 2);
    	EngNo->Text = MyCmd.SubString(5, 4);
    	//
    	Command->Text = MyCmd.SubString(9, 4);
        String C_Command = Convert16B8BNumber(Command->Text);
		String I_Command = bintodec8(bitswapRX(C_Command));
    	H_Command->Text = IntToHex(I_Command.ToInt(), 2);
    	Command->Text = MyCmd.SubString(9, 4);
    	// Decode first 4 data bytes
    	if (CommandLen > 30) {
	    	Data1->Text = MyCmd.SubString(13, 4);
	        String C_Data1 = Convert16B8BNumber(Data1->Text);
    		String D_Data1 = bintodec8(bitswapRX(C_Data1));
			H_Data1->Text = IntToHex(D_Data1.ToInt(), 2);
	    	Data1->Text = MyCmd.SubString(13, 4);
        	//
	    	if (CommandLen > 34) {
	   	    	Data2->Text = MyCmd.SubString(17, 4);
                String C_Data2 = Convert16B8BNumber(Data2->Text);
			    String D_Data2 = bintodec8(bitswapRX(C_Data2));
				H_Data2->Text = IntToHex(D_Data2.ToInt(), 2);
	   	    	Data2->Text = MyCmd.SubString(17, 4);
            	//
	        	if (CommandLen > 38) {
	              	Data3->Text = MyCmd.SubString(21, 4);
                    String C_Data3 = Convert16B8BNumber(Data3->Text);
				    String D_Data3 = bintodec8(bitswapRX(C_Data3));
					H_Data3->Text = IntToHex(D_Data3.ToInt(), 2);
	              	Data3->Text = MyCmd.SubString(21, 4);
   	              	//
	              	if (CommandLen > 42 ) {
	                    Data4->Text = MyCmd.SubString(25, 4);
                    	String C_Data4 = Convert16B8BNumber(Data4->Text);
					    String D_Data4 = bintodec8(bitswapRX(C_Data4));
						H_Data4->Text = IntToHex(D_Data4.ToInt(), 2);
	                    Data4->Text = MyCmd.SubString(25, 4);
                        //
                        }
                  	}
            	}
    		}
        // Sequence number, 4th byte from the end
    	Seq_Count->Text = MyCmd.SubString(CommandLen-17,4);
	   	String C_Seq_Count = Convert16B8BNumber(Seq_Count->Text);
		String D_Seq_Count = bintodec8(bitswapRX(C_Seq_Count));
	    H_Seq_Count->Text = IntToHex(D_Seq_Count.ToInt(), 2);
    	Seq_Count->Text = MyCmd.SubString(CommandLen-17,4);
    	//  TIU/Remote, 3rd byte from the end
    	TIU_Remote->Text = MyCmd.SubString(CommandLen-13,4);
		String C_TIU_Remote = Convert16B8BNumber(TIU_Remote->Text);
    	String D_TIU_Remote = bintodec8(bitswapRX(C_TIU_Remote));
		H_TIU_Remote->Text = IntToHex(D_TIU_Remote.ToInt(), 2);
    	TIU_Remote->Text = MyCmd.SubString(CommandLen-13,4);
    	//  LSB - Little Endian for the PC, 2nd byte from the end
    	CRC_LSB->Text = MyCmd.SubString(CommandLen-9,4);
    	String C_CRC_LSB = Convert16B8BNumber(CRC_LSB->Text);
    	String D_CRC_LSB = bintodec8(bitswapRX(C_CRC_LSB));
		H_CRC_LSB->Text = IntToHex(D_CRC_LSB.ToInt(), 2);
    	CRC_LSB->Text = MyCmd.SubString(CommandLen-9,4);
        // MSB, end byte
    	CRC_MSB->Text = MyCmd.SubString(CommandLen-5,4);
        String C_CRC_MSB = Convert16B8BNumber(CRC_MSB->Text);
    	String D_CRC_MSB = bintodec8(bitswapRX(C_CRC_MSB));
		H_CRC_MSB->Text = IntToHex(D_CRC_MSB.ToInt(), 2);
    	CRC_MSB->Text = MyCmd.SubString(CommandLen-5,4);
		//
    	CommandDecoded = true;
		return true;
    	}
   	}
CommandDecoded = false;
return false;
}
//---------------------------------------------------------------------------

/******************************************************************************/
/*                             Start of crcmodel.c                            */
/******************************************************************************/
/*                                        	                                  */
/* Author : Ross Williams (ross@guest.adelaide.edu.au.).                      */
/* Date   : 3 June 1993.                                                      */
/* Status : Public domain.                                                    */
/*                                                                            */
/* Description : This is the implementation (.c) file for the reference       */
/* implementation of the Rocksoft™ Model CRC Algorithm. For more              */
/* information on the Rocksoft™ Model CRC Algorithm, see the document         */
/* titled "A Painless Guide to CRC Error Detection Algorithms" by Ross        */
/* Williams (ross@guest.adelaide.edu.au.). This document is likely to be in   */
/* "ftp.adelaide.edu.au/pub/rocksoft".                                        */
/*                                                                            */
/* Note: Rocksoft is a trademark of Rocksoft Pty Ltd, Adelaide, Australia.    */
/*                                                                            */
/******************************************************************************/
/*                                                                            */
/* Implementation Notes                                                       */
/* --------------------                                                       */
/* To avoid inconsistencies, the specification of each function is not echoed */
/* here. See the header file for a description of these functions.            */
/* This package is light on checking because I want to keep it short and      */
/* simple and portable (i.e. it would be too messy to distribute my entire    */
/* C culture (e.g. assertions package) with this package.                     */
/*                                                                            */
/******************************************************************************/

/* The following definitions make the code more readable. */

#define BITMASK(X) (1L << (X))
#define MASK32 0xFFFFFFFFL
#define LOCAL static

/******************************************************************************/

//LOCAL ulong reflect P_((ulong v,int b));
LOCAL ulong reflect (ulong v,int b)
/* Returns the value v with the bottom b [0,32] bits reflected. */
/* Example: reflect(0x3e23L,3) == 0x3e26                        */
//ulong v;
//int   b;
{
 int   i;
 ulong t = v;
 for (i=0; i<b; i++)
   {
    if (t & 1L)
       v|=  BITMASK((b-1)-i);
    else
       v&= ~BITMASK((b-1)-i);
    t>>=1;
   }
 return v;
}

/******************************************************************************/

//LOCAL ulong widmask P_((p_cm_t));
LOCAL ulong widmask (p_cm_t p_cm)
/* Returns a longword whose value is (2^p_cm->cm_width)-1.     */
/* The trick is to do this portably (e.g. without doing <<32). */
//p_cm_t p_cm;
{
 return (((1L<<(p_cm->cm_width-1))-1L)<<1)|1L;
}

/******************************************************************************/

void cm_ini (p_cm_t p_cm)
//p_cm_t p_cm;
{
 p_cm->cm_reg = p_cm->cm_init;
}

/******************************************************************************/

void cm_nxt (p_cm_t p_cm, int ch)
//p_cm_t p_cm;
//int    ch;
{
 int   i;
 char os[CMDMAX];
 ulong uch  = (ulong) ch;
 ulong topbit = BITMASK(p_cm->cm_width-1);  // for 16 bit CRC topbit is 0x00008000

 if (p_cm->cm_refin) uch = reflect(uch,8);

 p_cm->cm_reg ^= (uch << (p_cm->cm_width-8));  // for 16 bit CRC, this is left shift 8
	if (Setup->CRC_Trace->Checked) {
		snprintf(os, sizeof os, "+ cm_nxt uch 0x%02X cm_reg 0x%04X cm_poly 0x%04X",
    		uch,
            p_cm->cm_reg,
            p_cm->cm_poly
            );
	    MainForm->CRCBuffer->SelAttributes->Color = clBlue;
    	MainForm->CRCBuffer->Lines->Append(os);
		ScrollEnd(MainForm->CRCBuffer);   // remain at end of buffer
    	}
 for (i=0; i<8; i++)
   {
    if (p_cm->cm_reg & topbit) {   // for 16 bit CRC topbit is 0x00008000
        p_cm->cm_reg = (p_cm->cm_reg << 1) ^ p_cm->cm_poly;
		if (Setup->CRC_Trace->Checked) {
		   MainForm->CRCBuffer->SelAttributes->Color = clBlue;
	       MainForm->CRCBuffer->Lines->Append("shift then XOR");
		   ScrollEnd(MainForm->CRCBuffer);   // remain at end of bufferer
           }
    	} else {
		if (Setup->CRC_Trace->Checked) {
		   MainForm->CRCBuffer->SelAttributes->Color = clBlue;
    	   MainForm->CRCBuffer->Lines->Append("shift");
		   ScrollEnd(MainForm->CRCBuffer);   // remain at end of buffer
           }
        p_cm->cm_reg <<= 1;
        }
    p_cm->cm_reg &= widmask(p_cm);

    // trace printout
	if (Setup->CRC_Trace->Checked) {
		snprintf(os, sizeof os, "- cm_nxt uch 0x%02X cm_reg 0x%04X cm_poly 0x%04X",
    		uch & 0xFF,
        	p_cm->cm_reg,
        	p_cm->cm_poly
        	);
	    MainForm->CRCBuffer->SelAttributes->Color = clBlue;
	    MainForm->CRCBuffer->Lines->Append(os);
		ScrollEnd(MainForm->CRCBuffer);   // remain at end of buffer
        }
    }
}

/******************************************************************************/
// this routine not used
void cm_blk (p_cm_t p_cm, p_ubyte_ blk_adr, ulong blk_len)
//p_cm_t   p_cm;
//p_ubyte_ blk_adr;
//ulong    blk_len;
{
 while (blk_len--) cm_nxt(p_cm, *blk_adr++);
}

/******************************************************************************/

ulong cm_crc (p_cm_t p_cm)
//p_cm_t p_cm;
{
 if (p_cm->cm_refot)
    return p_cm->cm_xorot ^ reflect(p_cm->cm_reg, p_cm->cm_width);
 else
    return p_cm->cm_xorot ^ p_cm->cm_reg;
}

/******************************************************************************/
// this routine not used
ulong cm_tab (p_cm_t p_cm, int index)
//p_cm_t p_cm;
//int    index;
{
 int   i;
 ulong r;
 ulong topbit = BITMASK(p_cm->cm_width-1);
 ulong inbyte = (ulong) index;

 if (p_cm->cm_refin) inbyte = reflect(inbyte,8);
 r = inbyte << (p_cm->cm_width-8);
 for (i=0; i<8; i++)
    if (r & topbit)
       r = (r << 1) ^ p_cm->cm_poly;
    else
       r<<=1;
 if (p_cm->cm_refin) r = reflect(r,p_cm->cm_width);
 return r & widmask(p_cm);
}

/******************************************************************************/
/*                             End of crcmodel.c                              */
/******************************************************************************/
//---------------------------------------------------------------------------
/*
Example of call from RetroXFMRE when creating a packet to send to the TIU
	// build command
	unsigned char Cmd_String[64];
	// Sync
	Cmd_String[0] = 0;
	// Engine Number
	Cmd_String[1] = EngineNo + 1;
	// Command
	int len = strlen((char *)Command_String);	// ASCII Command in Command_String, eg: "m4"
	for (int index = 0 ; index < len; index++) {
  		Cmd_String[2 + index] = Command_String[index];
  		}
	// Sequence Number
	Cmd_String[2 + len] = SeqNo++;
	// TIU / Remote
	Cmd_String[3 + len] = (((TIU - 1) & 0x000F) << 4) | (Remote & 0x000F);
	// calcualte CRC, skipping over the Sync
	unsigned int CRC = crc752(Cmd_String+1, (3 + len), false);
	byte CRCH = (CRC >> 8) & 0xFF;
	Cmd_String[4 + len] = CRCH;
	byte CRCL = CRC & 0xFF;
	Cmd_String[5 + len] = CRCL;

Example of call from RTCEngineE when creating a packet to send to the Remote

  const char bEM[] PROGMEM =  {0x00, 0x04, "0", "5", "0", "0"};  // TIU version Number (hardcoded)
  // calculate CRC
  unsigned char R[sizeof(bEM) + 1];
  int len = sizeof(bEM);  // not counting NULL
  BuildResponse(sizeof(bEM), bEM, R, true); =
  unsigned int CRC = crc752(R, len + 1, true);
  // Send CRC first
  byte CRCH = (CRC >> 8) & 0xFF;
  byte CRCL = CRC & 0xFF;
  SendBinaryResponse(CRCH);
  SendBinaryResponse(CRCL);
  // Send rest of Response packet
  for (int i = 0 ; i < len + 1 ; i++) {
    SendBinaryResponse(R[i]);	// convert to Morton, endcode and send the byte
    }
*/
//---------------------------------------------------------------------------
#define poly      0x8408    // Polynomial used to compute CRC
//
// CRC Calculator
// Based on routine written by Paul Reynolds, used with permission
//
// modified by Mark DiVecchio to handle larger packets and to handle both forward
// and reverse order CRC generation
//
unsigned int __fastcall crc752(byte message[], byte nBytes, bool Direction) {
// Compute crc using 16 bit MCRF45X with poly 0x8408, with left to right processing of input.
// Direction: true : packets from the TIU
//            false : packets to the TIU
//
unsigned int    remainder = 0xFFFF;  // Set remainder to initial remainder
short           bytePtr;
byte            bitCount, crcTemp, nextBit;
//byte maskreg[8] = {1, 2, 4, 8, 16, 32, 64, 128};
byte maskreg[8] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};

const short numBits = nBytes * 8;

// This FOR loop does all of the CRC computation work.
for (short i = 0; i < numBits; i++) {
    // process input byte from leftmost byte to rightmost byte
    bitCount = i & 0x07 ; // (was i % 8) repeatedly counts from zero to seven as FOR loop iterates
    bytePtr = i >> 3; // (was i / 8) points at bytes, from left to right, shifting left one every eight iterations.
    nextBit = (message[Direction ? bytePtr : nBytes - bytePtr - 1] & maskreg[bitCount]); //This will find the next data bit to apply, not yet shifted.
    nextBit = ((nextBit >> bitCount) & 0x01); //This will set nextBit to zero or one, depending on bit masked out, above.
    crcTemp = remainder ^ nextBit; //xor the last bit of remainder with next_bit
    if (crcTemp & 0x01) {
      remainder = remainder >> 1; //Shift the remainder by 1 to right
      remainder = remainder ^ poly ; //xor current remainder with chosen polynomial (poly)
      } else remainder = remainder >> 1;
  }
return Direction ? remainder : ((remainder & 0xFF00) >> 8) | ((remainder & 0x00FF) << 8);
}
//---------------------------------------------------------------------------

int __fastcall TMainForm::CreateCommandString(Cmd * Cmd_Array) // Create Command String from the HEX display boxes
{
	Cmd_Array->idx = 0;
    Cmd_Array->Str[Cmd_Array->idx++] = 0; // Sync
	Cmd_Array->Str[Cmd_Array->idx++] = (char) _tcstoul(H_EngNo->Text.c_str(), NULL , 16); // D_EngNo->Text.ToInt();
	Cmd_Array->Str[Cmd_Array->idx++] = (char) _tcstoul(H_Command->Text.c_str(), NULL , 16); // D_Command->Text.ToInt();
	if ((H_Data1->Text != "-") && (!H_Data1->Text.IsEmpty())){
		Cmd_Array->Str[Cmd_Array->idx++] = (char) _tcstoul(H_Data1->Text.c_str(), NULL , 16); // D_Data1->Text.ToInt();
        }
	if ((H_Data2->Text != "-") && (!H_Data2->Text.IsEmpty())){
		Cmd_Array->Str[Cmd_Array->idx++] = (char) _tcstoul(H_Data2->Text.c_str(), NULL , 16); // D_Data2->Text.ToInt();
        }
	if ((H_Data3->Text != "-") && (!H_Data3->Text.IsEmpty())){
		Cmd_Array->Str[Cmd_Array->idx++] = (char) _tcstoul(H_Data3->Text.c_str(), NULL , 16); // D_Data3->Text.ToInt();
		}
	if ((H_Data4->Text != "-") && (!H_Data4->Text.IsEmpty())){
		Cmd_Array->Str[Cmd_Array->idx++] = (char) _tcstoul(H_Data4->Text.c_str(), NULL , 16); // D_Data4->Text.ToInt();
        }
	Cmd_Array->Str[Cmd_Array->idx++] = (char) _tcstoul(H_Seq_Count->Text.c_str(), NULL , 16); // D_Seq_Count->Text.ToInt();
	Cmd_Array->Str[Cmd_Array->idx++] = (char) _tcstoul(H_TIU_Remote->Text.c_str(), NULL , 16); // D_TIU_Remote->Text.ToInt();
	Cmd_Array->Str[Cmd_Array->idx++] = (char) _tcstoul(H_CRC_LSB->Text.c_str(), NULL , 16); // D_CRC_LSB->Text.ToInt();
	Cmd_Array->Str[Cmd_Array->idx++] = (char) _tcstoul(H_CRC_MSB->Text.c_str(), NULL , 16); // D_CRC_MSB->Text.ToInt();
    Cmd_Array->Str[Cmd_Array->idx] = NULL;    // NULL Terminated
    Cmd_Array->ready = true;
return Cmd_Array->idx; // return item count in array
}
//---------------------------------------------------------------------------

unsigned int __fastcall TMainForm::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;
#ifdef MOREDEBUGGING
char os[MAX_PATH];
unsigned char const msb_byte_c1 = (crc_result & 0xFF00) >> 8;
unsigned char const lsb_byte_c1 = (crc_result & 0x00FF);
#endif

// 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 (DEBUGGING) {
	H_CRC_LSB->Text = IntToHex(Command->Str[EndChar+1], 2);
	H_CRC_MSB->Text = IntToHex(Command->Str[EndChar+2], 2);
    }

#ifdef MOREDEBUGGING
// This routine called from Comm Thread so these VCL calls should not be used
if (DEBUGGING) {
	// don't display CRC details
	snprintf(os, sizeof os, "CRC Gen : 0x%02X 0x%02X", lsb_byte_c1,	msb_byte_c1);
	if (!Quiet) DB->Debug_Print(os);
	if (!Quiet) {
		CRCBuffer->SelAttributes->Color = clBlue;
		CRCBuffer->Lines->Append(os);
		ScrollEnd(CRCBuffer);   // remain at end of buffer
		CRCBuffer->SelAttributes->Color = clBlue;
		CRCBuffer->Lines->Append ("Done.");
		ScrollEnd(CRCBuffer);   // remain at end of buffer
	    }
  	}
#endif
return crc_result;
}
//---------------------------------------------------------------------------

unsigned int __fastcall TMainForm::CRCCheck(Cmd * Cmd_String, int StartChar, int EndChar, bool Quiet)
{
//
// This function checks packets received from the Remote for CRC 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, Cmd_String->Str[ii]);
    }
// feed in the CRC bytes - LSB then MSB
cm_nxt(p_cm, Cmd_String->Str[EndChar+1]);
cm_nxt(p_cm, Cmd_String->Str[EndChar+2]);
//
ulong const crc_result = cm_crc(p_cm) & 0xFFFF;
#ifdef MOREDEBUGGING
// Can be used for Debugging
char os[MAX_PATH];
if (DEBUGGING) {
	unsigned char msb_byte_c1 = (crc_result & 0xFF00) >> 8;
	unsigned char lsb_byte_c1 = (crc_result & 0x00FF);
	// don't display CRC details
	DB->Debug_Print(os);
	if (!Quiet) {
		CRCBuffer->SelAttributes->Color = clBlue;
		CRCBuffer->Lines->Append(os);
		ScrollEnd(CRCBuffer);   // remain at end of buffer
		CRCBuffer->SelAttributes->Color = clBlue;
		CRCBuffer->Lines->Append ("Done.");
		ScrollEnd(CRCBuffer);   // remain at end of buffer
	    }
    }
#endif
return crc_result;
}
//---------------------------------------------------------------------------

unsigned int __fastcall TMainForm::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

#ifdef MOREDEBUGGING
// Can be used for Debugging
char os[MAX_PATH];
if (DEBUGGING) {
	unsigned char msb_byte_c1 = (crc_result & 0xFF00) >> 8;
	unsigned char lsb_byte_c1 = (crc_result & 0x00FF);
	snprintf(os, sizeof os, "CRC Eval : 0x%02X 0x%02X", lsb_byte_c1, msb_byte_c1);
    DB->Debug_Print(os);

	if (crc_result != 0x0000) {
        DB->Debug_Print("CRC Error", clRed);
        Quiet = false;	// to display the result
    	}
	if (!Quiet) {
		MainForm->CRCBuffer->SelAttributes->Color = clBlue;
        if (crc_result != 0x0000) {
        	CRCBuffer->Lines->Append("CRC Error");
			ScrollEnd(CRCBuffer);   // remain at end of buffer
            }
		CRCBuffer->Lines->Append(os);
		CRCBuffer->SelAttributes->Color = clBlue;
		ScrollEnd(CRCBuffer);   // remain at end of buffer
	    }
    }
#endif
return crc_result;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------

unsigned int __fastcall TMainForm::CRCEvaluation(byte * 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[ii]);
    }
// feed in the CRC bytes - LSB then MSB     ******
cm_nxt(p_cm, Cmd_String[StartChar+1]);
cm_nxt(p_cm, Cmd_String[StartChar]);
//
ulong const crc_result = cm_crc(p_cm) & 0xFFFF;

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

#ifdef MOREDEBUGGING
// This routine called from Comm Thread so these VCL calls should not be used
char os[MAX_PATH];
if (DEBUGGING) {
	unsigned char msb_byte_c1 = (crc_result & 0xFF00) >> 8;
	unsigned char lsb_byte_c1 = (crc_result & 0x00FF);
	snprintf(os, sizeof os, "CRC Eval : 0x%02X 0x%02X", lsb_byte_c1, msb_byte_c1);
    DB->Debug_Print(os);

	if (crc_result != 0x0000) {
        DB->Debug_Print("CRC Error", clRed);
        Quiet = false;	// to display the result
    	}
	if (!Quiet) {
		MainForm->CRCBuffer->SelAttributes->Color = clBlue;
        if (crc_result != 0x0000) {
        	CRCBuffer->Lines->Append("CRC Error");
			ScrollEnd(CRCBuffer);   // remain at end of buffer
            }
		CRCBuffer->Lines->Append(os);
		CRCBuffer->SelAttributes->Color = clBlue;
		ScrollEnd(CRCBuffer);   // remain at end of buffer
	    }
    }
#endif
return crc_result;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ProcessCRCButtonClick(TObject *Sender)
{
Cmd Command;
PrintCommand();	// print the command
switch (CRCSel->ItemIndex) {
	case 0:
		Command.idx = CreateCommandString(&Command);	// Fill Cmd_String from the Display Boxes
		(void) CRCGen(&Command, 1, Command.idx - 3, false);	// from Cmd_String - Generate CRC's - verbosely
		break;
    case 1:
		Command.idx = CreateCommandString(&Command);	// Fill Cmd_String from the Display Boxes
		(void) CRCCheck(&Command, 1, Command.idx - 3, false);// from Cmd_String - Check CRC - verbosely
		break;
	}
}
//---------------------------------------------------------------------------

bool __fastcall TMainForm::PrintCommandString(String Command) {
    Command = CleanUpCommand(Command);
	if (ValidCommand(Command)) {
	// then Decode it
		if (DecodeMyCommand(Command)) {
            // insert decoded command into the CRCBuffer
            TCHAR os[100];
			_sntprintf(os, sizeof os, TEXT("   0%5d%5d  %3s  %3s  %3s  %3s%5d%5d   %02X   %02X"),	
            	D_EngNo->Text.ToInt(),
                D_Command->Text.ToInt(),
				D_Data1->Text == "-" ? TEXT("   ") : D_Data1->Text.c_str(),
				D_Data2->Text == "-" ? TEXT("   ") : D_Data2->Text.c_str(),
				D_Data3->Text == "-" ? TEXT("   ") : D_Data3->Text.c_str(),
                D_Data4->Text == "-" ? TEXT("   ") : D_Data4->Text.c_str(),
                D_Seq_Count->Text.ToInt(),
                D_TIU_Remote->Text.ToInt(),
                D_CRC_LSB->Text.ToInt(),
                D_CRC_MSB->Text.ToInt()
                );
		    CRCBuffer->SelAttributes->Color = clGreen;
            CRCBuffer->Lines->Append((String)os);
			ScrollEnd(CRCBuffer);   // remain at end of buffer
        	}
    	}
return true;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ClearCRCButtonClick(TObject *Sender)
{
CRCBuffer->Clear();
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ProcessCRCIntoCRCBufferClick(TObject *Sender)
{
// Remote buffer popup menu item
// for the entire Remote buffer, decode each line then run the CRC generator on it
// All results into CRCBuffer	
// Decode all commands in the Main Buffer and write them to the CRC Buffer
if (DEBUGGING) STOPem->Enabled = true;
bStop = false;
//
const int count = RFIDBuffer->Lines->Count;

for (int idxdb = 0 ; idxdb < count ; idxdb++) {
    if (bStop) {
       	break;
        }
	String Command = RFIDBuffer->Lines->Strings[idxdb];
    CRCBuffer->SelAttributes->Color = clBlue;
   	CRCBuffer->Lines->Append(Command);
    Command = CleanUpCommand(Command);
	ScrollEnd(CRCBuffer);   // remain at end of buffer
	if (ValidCommand(Command)) {
        // Decode command found on line
        DecodeMyCommand(Command);
		ProcessCRCButton->OnClick(ProcessCRCButton);
    	}
	Application->ProcessMessages();
	}
STOPem->Enabled = false;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::H_EngNoChange(TObject *Sender)
{
if ((H_EngNo->Text != "-") && (!H_EngNo->Text.IsEmpty())) {
    D_EngNo->Text = _tcstoul(H_EngNo->Text.c_str(), NULL , 16);
    // then clear the Encoded field because it's not valid anymore
    EngNo->Text = "-";
    } else {
    D_EngNo->Text = "-";
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::H_CommandChange(TObject *Sender)
{
if ((H_Command->Text != "-") && (!H_Command->Text.IsEmpty())) {
    D_Command->Text = _tcstoul(H_Command->Text.c_str(), NULL , 16);
    A_Command->Text = (char)D_Command->Text.ToInt();  // Convert to ASCII Character
    A_Command->Text = "'" + A_Command->Text + "'";
    // then clear the Encoded field because it's not valid anymore
    Command->Text = "-";
    } else {
    D_Command->Text = "-";
    A_Command->Text = "-";
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::H_Seq_CountChange(TObject *Sender)
{
if ((H_Seq_Count->Text != "-") && (!H_Seq_Count->Text.IsEmpty())) {
    D_Seq_Count->Text = _tcstoul(H_Seq_Count->Text.c_str(), NULL , 16);
    // then clear the Encoded field because it's not valid anymore
    Seq_Count->Text = "-";
    } else {
    D_Seq_Count->Text = "-";
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::H_TIU_RemoteChange(TObject *Sender)
{
if ((H_TIU_Remote->Text != "-") && (!H_TIU_Remote->Text.IsEmpty())) {
    D_TIU_Remote->Text = _tcstoul(H_TIU_Remote->Text.c_str(), NULL , 16);
    // then clear the Encoded field because it's not valid anymore
    TIU_Remote->Text = "-";
    } else {
    D_TIU_Remote->Text = "-";
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::H_CRC_LSBChange(TObject *Sender)
{
if ((H_CRC_LSB->Text != "-") && (!H_CRC_LSB->Text.IsEmpty())) {
    D_CRC_LSB->Text = _tcstoul(H_CRC_LSB->Text.c_str(), NULL , 16);
    // then clear the Encoded field because it's not valid anymore
    CRC_LSB->Text = "-";
    } else {
    D_CRC_LSB->Text = "-";
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::H_CRC_MSBChange(TObject *Sender)
{
if ((H_CRC_MSB->Text != "-") && (!H_CRC_MSB->Text.IsEmpty())) {
    D_CRC_MSB->Text = _tcstoul(H_CRC_MSB->Text.c_str(), NULL , 16);
    // then clear the Encoded field because it's not valid anymore
    CRC_MSB->Text = "-";
    } else {
    D_CRC_MSB->Text = "-";
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::H_Data1Change(TObject *Sender)
{
if ((H_Data1->Text != "-") && (!H_Data1->Text.IsEmpty())) {
    D_Data1->Text = _tcstoul(H_Data1->Text.c_str(), NULL , 16);
    A_Data1->Text = (char)D_Data1->Text.ToInt();  // Convert to ASCII Character
    A_Data1->Text = "'" + A_Data1->Text + "'";
    // then clear the Encoded field because it's not valid anymore
    Data1->Text = "-";
    } else {
    D_Data1->Text = "-";
    A_Data1->Text = "-";
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::H_Data2Change(TObject *Sender)
{
if ((H_Data2->Text != "-") && (!H_Data2->Text.IsEmpty())) {
    D_Data2->Text = _tcstoul(H_Data2->Text.c_str(), NULL , 16);
    A_Data2->Text = (char)D_Data2->Text.ToInt();  // Convert to ASCII Character
    A_Data2->Text = "'" + A_Data2->Text + "'";
    // then clear the Encoded field because it's not valid anymore
    Data2->Text = "-";
    } else {
    D_Data2->Text = "-";
    A_Data2->Text = "-";
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::H_Data3Change(TObject *Sender)
{
if ((H_Data3->Text != "-") && (!H_Data3->Text.IsEmpty())) {
    D_Data3->Text = _tcstoul(H_Data3->Text.c_str(), NULL , 16);
    A_Data3->Text = (char)D_Data3->Text.ToInt();  // Convert to ASCII Character
    A_Data3->Text = "'" + A_Data3->Text + "'";
    // then clear the Encoded field because it's not valid anymore
    Data3->Text = "-";
    } else {
    D_Data3->Text = "-";
    A_Data3->Text = "-";
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::H_Data4Change(TObject *Sender)
{
if ((H_Data4->Text != "-") && (!H_Data4->Text.IsEmpty())) {
    D_Data4->Text = _tcstoul(H_Data4->Text.c_str(), NULL , 16);
    A_Data4->Text = (char)D_Data4->Text.ToInt();  // Convert to ASCII Character
    A_Data4->Text = "'" + A_Data4->Text + "'";
    // then clear the Encoded field because it's not valid anymore
    Data4->Text = "-";
    } else {
    D_Data4->Text = "-";
    A_Data4->Text = "-";
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ClearTIUButtonClick(TObject *Sender)
{
// clear TIU Buffer
TIUBuffer->Clear();
// Clear TIU Buffer Byte Count
TIU_Char_Count->Text = "0";
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ClearRemoteButtonClick(TObject *Sender)
{
// clear RFID Buffer
RFIDBuffer->Clear();
// Clear Received Byte Count
//iRFIDbufCount = 0;
// Clear RFID Port Buffer Byte Count
RFID_Char_Count->Text = "0";
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::Clear_Decode_ButtonClick(TObject *Sender)
{
ClearDecode();	// Clear all fields	
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CutFromCRCBufferClick(TObject *Sender)
{
// CRC Buffer popup menu item
CRCBuffer->CutToClipboard();
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CopyFromCRCBufferClick(TObject *Sender)
{
// CRC Buffer popup menu item
CRCBuffer->CopyToClipboard();
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::PasteIntoCRCBufferClick(TObject *Sender)
{
// CRC Buffer popup menu item
CRCBuffer->PasteFromClipboard();
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::SelectAllCRCBufferClick(TObject *Sender)
{
// CRC Buffer popup menu item
CRCBuffer->SelectAll();
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CutFromTIUBufferClick(TObject *Sender)
{
// TIU Buffer popup menu item
TIUBuffer->CutToClipboard();
}
//---------------------------------------------------------------------------
void __fastcall TMainForm::CopyFromTIUBufferClick(TObject *Sender)
{
// TIU Buffer popup menu item
TIUBuffer->CopyToClipboard();
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::PasteIntoTIUBufferClick(TObject *Sender)
{
// TIU Buffer popup menu item
// Text is Red
TIUBuffer->SelAttributes->Color = clRed;
TIUBuffer->PasteFromClipboard();
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::SelectAllTIUBufferClick(TObject *Sender)
{
// TIU Buffer popup menu item
TIUBuffer->SelectAll();
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CutFromRFIDBufferClick(TObject *Sender)
{
// Remote Buffer popup menu item
RFIDBuffer->CutToClipboard();
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::AppendCRCBuffertoRemoteBuffer1Click(TObject *Sender)
{
// CRC Buffer popup menu item
RFIDBuffer->SelStart = RFIDBuffer->GetTextLen();	// Cursor to end of text
// copy CRC Buffer to Main Buffer
CRCBuffer->CopyToClipboard();
RFIDBuffer->PasteFromClipboard();
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::DecodeStringClick(TObject *Sender)
{
// Remote Buffer and TIU Buffer popup menu item
// Decode an encoded string
// This will decode and print out a string regardless of the contents.
// It will process the entire string to the end
//
//If there is a selected text active, then decode that selected text
//Decode the selected string from the Main Buffer
//

TRichEdit * SelectedBuffer;
TPopupMenu * MenuUsed;
MenuUsed = (TPopupMenu *)Sender;
if (MenuUsed->Name.Pos("RFID")) SelectedBuffer = RFIDBuffer;
if (MenuUsed->Name.Pos("TIU")) SelectedBuffer = TIUBuffer;

TPoint CaretPos = SelectedBuffer->CaretPos;
String OriginalString = SelectedBuffer->Lines->Strings[CaretPos.y];
//
String MyString = SelectedBuffer->SelText;
if (MyString.IsEmpty()) {
	// otherwise, if the cursor is on a line with a valid string,
    // then decode the command on that line
    // get MyString from the cursor line
    MyString = CleanUpCommand(OriginalString);
    } else {
    // Selection Active, so use the selection
    MyString = CleanUpCommand(MyString);
    }
//
CRCBuffer->SelAttributes->Color = SelectedBuffer->SelAttributes->Color;	// Copy with same color
CRCBuffer->Lines->Append(OriginalString);
ScrollEnd(CRCBuffer);   // remain at end of buffer
(void) sDecodeString(MyString, true); // Decode and print (into CRCBuffer) the command in MyString
}
//---------------------------------------------------------------------------

String __fastcall TMainForm::sDecodeString(String AnyString, bool PrinttoCRCBuffer)
{
//
// Decode any string, if PrinttoCRCBuffer, print to CRC Buffer
//
//if (DEBUGGING) DB->Debug_Print("DecodeString: " + AnyString);
String os = "", oc = "";
if (!AnyString.IsEmpty()) {
    // break the string into 4 character groups and print the values in
    // hex and (if possible) as a character
	int SLen = AnyString.Length();
	if (SLen == 0) return "";
    // only process strings which start with a valid Encoded Character 5, 6, 9, or A
    if (!AnyString.IsDelimiter("569A", 1)) return "";
    //
    for (int idx = 1 ; idx < SLen; idx += 4) {
       	if (SLen - idx < 3) continue; // don't decode unless we have 4 characters
		String sVal = bintodec8(bitswapRX(Convert16B8BNumber(AnyString.SubString(idx, 4))));
        int iVal = sVal.ToInt();	
	   	String hVal = IntToHex(iVal & 0xFF, 2);
        os += "  ";
    	os += hVal;
    	// print string byte in ASCII
    	String A_Char;
        if (iVal > 32 && iVal < 128) {
	        A_Char = (char)iVal;  // Convert to ASCII Character
			oc = oc + " '" + A_Char + "'";
            } else {
            oc += "    ";
            }
        }
    if (PrinttoCRCBuffer) {
    	CRCBuffer->SelAttributes->Color = clBlue;	// Blue for Decodes
	    CRCBuffer->Lines->Append(os);
	    CRCBuffer->SelAttributes->Color = clBlue;	// Blue for Decodes
	   	CRCBuffer->Lines->Append(oc);
        ScrollEnd(CRCBuffer);   // remain at end of buffer
        }
	}
return os;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------

String __fastcall byteDecodeBinaryString(byte * AnyString, int Len, bool PrinttoCRCBuffer)
{
//
// Decode a binary string, if PrinttoCRCBuffer, print to CRC Buffer
// string can contain a binary 0x00 since a length is passed in the call
//
String os = "", oc = "";
if (AnyString != NULL) {
    // print the values in hex and (if possible) as a character
	if (Len == 0) return "";
    //
    for (int idx = 0 ; idx < Len; idx += 2) {
       	if (Len - idx < 1) continue; // don't decode unless we have 2 bytes
        int iVal = RXLookUp(AnyString[idx] << 8 | AnyString[idx + 1]);
	   	String hVal = IntToHex(iVal & 0xFF, 2);
        os += "  ";
    	os += hVal;
    	// print string byte in ASCII
    	String A_Char;
        if (iVal > 32 && iVal < 128) {
	        A_Char = (char)iVal;  // Convert to ASCII Character
			oc = oc + " '" + A_Char + "'";
            } else {
            oc += "    ";
            }
        }
    /*
    if (PrinttoCRCBuffer) {
    	CRCBuffer->SelAttributes->Color = clBlue;	// Blue for Decodes
	    CRCBuffer->Lines->Append(os);
		ScrollEnd(CRCBuffer);   // remain at end of buffer
	    CRCBuffer->SelAttributes->Color = clBlue;	// Blue for Decodes
	   	CRCBuffer->Lines->Append(oc);
        ScrollEnd(CRCBuffer);   // remain at end of buffer
        }
    */
	}
return os;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::DecodeStringBufferClick(TObject *Sender)
{
// Remote Buffer and TIU Buffer popup menu item
TRichEdit * SelectedBuffer;
TPopupMenu * MenuUsed;
MenuUsed = (TPopupMenu *)Sender;
if (MenuUsed->Name.Pos("RFID")) SelectedBuffer = RFIDBuffer;
if (MenuUsed->Name.Pos("TIU")) SelectedBuffer = TIUBuffer;
// Decode all strings in the Buffer and write them to the CRC Buffer
//
if (DEBUGGING) STOPem->Enabled = true;
bStop = false;
//
const int count = SelectedBuffer->Lines->Count;

for (int idxdb = 0 ; idxdb < count ; idxdb++) {
   if (bStop) {
        break;
        }
	String MyString = SelectedBuffer->Lines->Strings[idxdb];
    CRCBuffer->SelAttributes->Color = SelectedBuffer->SelAttributes->Color;	// Copy with same color
   	CRCBuffer->Lines->Append(MyString);
    MyString = CleanUpCommand(MyString);
    ScrollEnd(CRCBuffer);   // remain at end of buffer
    //
	(void) sDecodeString(MyString, true); // Decode and print (into CRC Buffer) the string in MyString
    Application->ProcessMessages();
	}
STOPem->Enabled = false;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::RTSSwitchClick(TObject *Sender)
{
if (!IsRadioCOMConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
//ptrTIUCommThread->RTSSwitch(RTSSwitch->Checked);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::DTRSwitchClick(TObject *Sender)
{
if (!IsRadioCOMConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
//ptrTIUCommThread->DTRSwitch(DTRSwitch->Checked);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::DTRSwitchStatusChanged(TObject *Sender)
{
ptrTIUCommThread->DTRSwitch(DTRSwitch->Checked);
if (DEBUGGINGX(6)) DB->Debug_Print("DTRSwitchStatus: " + ConnectedWiFiRadioLED->Caption + " DTR " + (int)DTRSwitch->Checked);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::RTSSwitchStatusChanged(TObject *Sender)
{
ptrTIUCommThread->RTSSwitch(RTSSwitch->Checked);
if (DEBUGGINGX(6)) DB->Debug_Print("RTSSwitchStatus: " + ConnectedWiFiRadioLED->Caption + " RTS " + (int)RTSSwitch->Checked);
}
//---------------------------------------------------------------------------

String sEncodeAString(Cmd CS)
{
// Encode a command into a string that can be transmitted
// NO VCL calls in this function
String Encoded_Command;
// encode a command from CS - then the command can be sent - NOTE : CS may contain binary zero
// Sync Byte at the beginning
Encoded_Command = "5555";
// Encode the command
for (int i = 0 ; i < CS.idx ; i++) {
	Encoded_Command = Encoded_Command + Convert8B16BNumber(bitswapTX(dectobin8(CS.Str[i])));
	}
// EOT
Encoded_Command = Encoded_Command + "4D";
//if (DEBUGGING) DB->Debug_Print("EncodeAString: " + Encoded_Command);
return Encoded_Command;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::UtilitiesButtonClick(TObject *Sender)
{
// This code should work with auto create on OR off.
// ------------ With programmed create using C++ Builder's generated pointer
// Utiliies Form should be manually created as it is rarely needed
if (!Utilities) {
	Utilities = new TUtilities(this);     // Pass "this" as the owner
	assert(Utilities != NULL);			// make sure that construction worked
   	}
Utilities->Show();	// since I used the C++ pointer, it will get automatically deleted at close
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::SetupButtonClick(TObject *Sender)
{
assert(Setup != NULL);		// setup must be created
// Setup Form is auto-create
Setup->Show();
if (Setup->DisableEmergencyStopButton->Checked) {
	ESTOP->Visible = false;
    ESTOP->Enabled = false;
    ESTOP->Hint = "Disabled in Setup";
    ESTOP->Cursor = crNo;
    }
Ignore_FF_Char = Setup->Ignore_FF->Checked;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::HiddenDblClick(TObject *Sender)
{
// --------------------------------------------------------
// Doot, doot, doo, lookin' out my back door -- John Fogerty
// --------------------------------------------------------
DebugButton->OnClick(DebugButton);
}
//---------------------------------------------------------------------------
void __fastcall TMainForm::DebugButtonClick(TObject *Sender)
{
// This code should work with auto create on OR off.
// ------------ With programmed create using C++ Builder's generated pointer
// Debug Form should be auto create because the DB pointer is used everywhere
if (!DB) {
	DB = new TDB(this);     // Pass "this" as the owner
	assert(DB != NULL);		// make sure that construction worked
    }
// since I used the C++ pointer, it will get automatically deleted at close
DB->ShowIt();
//
SetZOrder(true); 	// put window behind main form
// open up mainform
Width = SavedDebugWidth;
Height = SavedDebugHeight;
DebugPageControl->ActivePage = WorkingTabSheet;
LEDPageControl->ActivePage = LEDTabSheet;

// Display ThreadId in debug buffer
if (DEBUGGING) {
	DB->Debug_Print("TMainForm:MainThreadID " + IntToHex((int)MainThreadID, 8), clGreen);
   	// MainThreadID and GetCurrentThreadId() return the same value
	//DB->Debug_Print("TMainForm:ThreadID " + IntToHex((int)GetCurrentThreadId(), 8), clGreen);
       // MainInstance returns 0x00400000
	//DB->Debug_Print("TMainForm:MainInstance 0x" + IntToHex((int)MainInstance, 8), clGreen);
       // IsMultiThread returns true
	//DB->Debug_Print("TMainForm:IsMultiThread " + String(IsMultiThread ? "true" : "false"), clGreen);
	/*
    http://docwiki.embarcadero.com/RADStudio/Rio/en/Predefined_Macros
    http://docwiki.embarcadero.com/RADStudio/Rio/en/Predefined_Macros#C.2B.2B_Compiler_Versions_in_Predefined_Macros

	Compiler 			__BORLANDC__ value
	Borland C++ Builder 4 	0x0540
	Borland C++ Builder 5 	0x0550
	Borland C++ 5.5 		0x0550
	Borland C++ 5.5.1 		0x0551
	Borland C++ Builder 6 	0x0560
	Developer Studio 2006	0x0570
	C++Builder Seattle 10	0x0710
	C++Builder Berlin 10.1	0x0720
	C++Builder Tokyo 10.2	0x0730
	C++Builder Rio 10.3		0x0740
	*/
	DB->Debug_Print("__BORLANDC__ 0x" + IntToHex(__BORLANDC__, 4));
	}
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::StartupButtonClick(TObject *Sender)
{
// https://stackoverflow.com/questions/31649315/c-builder-button-click-function-reentry
LOCKEM()

// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
	}

// This can be called even if the Engine is already running. It just restores windows
// Allow Engines or All Engines Only
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
assert(EngineNo >= (Setup->AllowZero->Checked? 0 : 1) && EngineNo <= ALLENGINE);	// 0 factory reset, 1-99, 100 "all engine operation"
int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
    return;
    }
if (EngineNo == ALLENGINE) {
    // in AllEngineMode, send the Startup commands to all engines on all TIU (MyTIUNo is not used)
    MyTIUNo = TIUSpin->ValueAsInt;
    if (ActiveEngineCount->ValueAsInt == 0) {
        DebugMessagePopup("Error", "All Engine operation requires at least one active engine");
        return;
        } else {
        DebugMessagePopup("Information", "Only active engines participate in All Engine operation");
        Delay(UIPOPUPTIMEOUT);
        }
    } else {
    // TIUNo of selected Engine
    MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineData->Row].ToIntDef(0);
    if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
        DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineData->Row));
        // fail if the TIU number is not set by [Read] button
        return;
        }
    if (!TIUSelection[MyTIUNo - 1]) {
        DebugMessagePopup("Warning", "TIU #" + String(MyTIUNo) + " associated with this engine is not active", "");
        return;
        }
    }

AltKeyState = StartupButton->Focused() ? GetKeyState(VK_MENU) : 0;		// Run Startup All
ShiftKeyState = StartupButton->Focused() ? GetKeyState(VK_SHIFT) : 0;	// Use Extended Startup
//CtrlKeyState = StartupButton->Focused() ? GetKeyState(VK_CONTROL) : 0;	// Not used yet
// key pressed =  0xFF8X
// key not pressed = 0x000X
// "X" can be 0 or 1 - indicated key was toggled
// if either ALT key is pressed whilst clicking "Startup" button, run "Startup All"
if (AltKeyState < 0) {
	StartupActiveEngines1->OnClick(StartupButton);
	AltKeyState = ShiftKeyState = CtrlKeyState = 0;
    return;
    }
StartupButton->Enabled = false;
//
while(1) {
    for (int iPage = 1; iPage <= QUICKPAGEMAX ; iPage++) { // Quick Controls 1-5
        if (ptrQuick[iPage - 1] ) {
            // Quick Controls Page is open
            if (ptrQuick[iPage - 1] ->RunningState[EngineNo] == clLime) {
                EngineStartUp(EngineNo, MyTIUNo, true, 0); 	// engine is already started so don't send actual startup commands
                break;
                }
            }
        }
    EngineStartUp(EngineNo, MyTIUNo, false, 0);   // Startup Engine: Send startup commands and switch setup
    break;
    }
//
AltKeyState = ShiftKeyState = CtrlKeyState = 0;
StartupButton->Enabled = true;
}
//---------------------------------------------------------------------------

String __fastcall TMainForm::CreateJ13HexString(void)
{
// go through the Engine List and pull out all active engines
// create a "J" command with the bit set for each engine
//
// Add active engines to a 100 bit binary string, eg:
// "0000000000000000000000000000000000000000000000000000000000000000000000000000000100100000000000000000"
// Initialize the 100 bit string
OneHundredBitString = "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";

// loop through all engines
// start with next to LSB of string, which is DCS engine #2 (LSB of string is DCS engine #1 or a Factory Reset Engine
// go through the string from DSC engine #2 to DCS engine #99  (Engines 1-98)
for (int DCSEngNo = 2 ; DCSEngNo <= ENGINEMAX ; DCSEngNo++) {  // DCS Engines 2 through 99 (max 98 of them). Skip DCS #100
                         // must be one higher than actual Engine Number
    // TODO : Due to error in TIU, if any AIU is connected, then
    // the bit for engine 99 (DCS 100) is on. Need to skip that bit for now
    // that is, check only up to Engine 98 (DCS 99)
    // That was not enough because now I've seen that the cell phone app limits
    // the engine number to 97. I may have to disable both engines 99 and 98
    // The question becomes why was engine 98 also disabled in the remote? Maybe
    // that bit is used for something else also.

    // if the engine is active, set the appropriate bit in the OneHundredBitString
    if (EngineData->Cells[ENG_STATE][DCSEngNo - 1] == ACTIVE) {
        OneHundredBitString[(ALLENGINE + 1) - DCSEngNo] = '1';	// The [] operator assumes a base index of 1.
        }
    }

if (DEBUGGING) DB->Debug_Print("CreateJ13HexString - OneHundredBitString :  " + OneHundredBitString);
// Count the number of 1 bits on in the Binary string
int EngCount = OneBitsinBinaryString(OneHundredBitString);

DebugMessage("All Engine Operation - Found " + String(EngCount) + " Active Engine(s)");

// convert the OneHundredBitString into a "J" command
String TwentySixHexString =  "0";	// since only 25 bytes come from the OneHundredBitString, we need to start off with 4 bits of zeros
for (size_t i = 1 ; i <= ALLENGINE ; i = i + 4) {
    // eg: convert each 4 bits "0101" to a hex digit "5" and append onto the TwentySixHexString. The [] operator assumes a base index of 1.
    byte bval = (OneHundredBitString[i + 0] == '1' ? 8 : 0) +
                (OneHundredBitString[i + 1] == '1' ? 4 : 0) +
                (OneHundredBitString[i + 2] == '1' ? 2 : 0) +
                (OneHundredBitString[i + 3] == '1' ? 1 : 0);
    TwentySixHexString += IntToHex(bval, 1);
    }

assert(TwentySixHexString.Length() == 26);
// "J00000000000000000000000122" each bit counting from the right is an engine
return "J" + TwentySixHexString;   // 13 hex digits (26 characters)
//
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::EngineStartUp(int Engine, int TIU, bool AlrdyStrted, int iDelayTimeSeconds)
{
// startup engine given by Engine on TIU
// if AlrdyStrted is true, RTC will not send "u4"/"u6", "d0" or "s0" commands.
//
// RTC will still send commands for all engine settings as stored in the Engine List
// handles Engines from 0 to 100 (Factory Reset, 1-99, 100=All Engine Operation)
// Delay Time in Seconds
//
assert(Engine >= (Setup->AllowZero->Checked? 0 : 1) && Engine <= ALLENGINE);	// 0, 1-99, 100

if (!IsTIUConnected()) return;	// if TIU Port is not connected fail

const TCursor OldCursor = Screen->Cursor; 	// save cursor
Screen->Cursor = crHourGlass;
//
RequestyCommand = true;
//
// show Operation Form
// Start the Operations form for engines 0-100
//
// make sure that the engine is on the track
//
// If engine does not respond, show an error and don't start the operations window
//
int respCode = UNDETERMINED;
if (!AlrdyStrted) {
    //---------------------------------------------------------------------------
    //---------------------------------------------------------------------------
    //
    // Check for All Engine Mode
    //
    if (Engine == ALLENGINE && IsTIUConnected()) {
        //
        if (Setup != NULL && Setup->Radio_COM_Mode && Setup->TXOnlyComm->Checked) {	// cannot do this function without full duplex communications
            DebugMessagePopup("Warning", MainForm->TXMsg, "");
            Screen->Cursor = OldCursor;		// restore cursor
            return;
            }

        // updated for All Engine Mode using multiple TIU
        // when sending a command to each TIU,
        // presend the J13HexString
        //
        J13HexString = CreateJ13HexString();
        ptrTIUCommThread->Send_String_FIFO(Engine, J13HexString.c_str(), TIU, RemoteNo, 11, SHORT_WAIT, RECORD_OK, NULL, &respCode);
        }
    // The J13HexString setup is complete, move on actually starting the engines
    // set DIR forward  "d0"
	bool retv = ptrTIUCommThread->Send_String_FIFO(Engine, "d0", TIU, RemoteNo, 11, SHORT_WAIT, RECORD_OK, NULL, &respCode);
	/* Radio Mode
     Last Response  : 5996996659595565 (0x10)
                    :   49  99  0C  10
                                    ^^  Byte 3 of the response should be 0x10 to 0x1X (TIU number), 0x00 is engine not on track
       WiFi Mode
     Last Response  : d0 okay
	*/
	if (!retv) {
    	// d0 command failed
		new TMessageDisplayThread(false, this, Handle, "Warning", "Engine " + String(Engine) + " not on track (Engine Start Up)", "", UIPOPUPTIMEOUT);
        Screen->Cursor = OldCursor;		// restore cursor
		return;	// startup failed - don't show Op window
		}
    }   // if (!AlrdyStrted)

// Enable controls until Engine Number is changed or shutdown occurs
SoundCtrlsButton->Enabled = true;
OpButton->Enabled = true;

// Manually create form and show it
// Many Operation Control Forms - if the form is not already created, then create it
if (ptrOp[Engine] == NULL) {	// Engine 0-100 (0 is factory reset, 1-99, 100 for "all engine operation")
	// Create the Operation Screen
	if (ptrLUEngineList[Engine] != NULL) {
		delete[] ptrLUEngineList[Engine];	// if it's left over, delete the string - pointer to a char array
		ptrLUEngineList[Engine] = NULL;		// never a LashUpEngineList for a regular engine
        }
	if (ptrLUNameList[Engine] != NULL) {
    	delete[] ptrLUNameList[Engine];		// if it's left over, delete the string - pointer to a char array
		ptrLUNameList[Engine] = NULL;		// never an Engine Name for a regular engine
        }
	ptrOp[Engine] = new TOp(this, RemoteNo, Engine, TIU, EngineData->Cells[ENG_NAME][Engine], NULL);   // create the Form and show it
								// Pass "this" as the owner
    							// 1-99 Engine Number, 100 "all engine operaton"
	assert(ptrOp[Engine] != NULL);	// make sure that construction worked
    ptrOp[Engine]->ParentHandle = Handle; // for return messages
    //
    // Begin Startup Command sequence
    //
    // This is a guess at determining if this is a PS2 or PS3 engine. It appears that RAM 0x50 at the
    // start is ==0x00 for all PS3 engines and !=0x00 for all PS2 engines. This might not be always true.
    //
    if (DEBUGGING || (EngineData->Cells[ENG_PS][Engine] != PS2 && EngineData->Cells[ENG_PS][Engine] != PS3)) {
	    byte ival;
    	bool bretv = FetchByte(0x50, &ival, Engine, TIU);
        if (!bretv)
            ival = 0;           // default
	    if (ival == 0x00) {
    	    EngineData->Cells[ENG_PS][Engine] = PS3;
            if (DEBUGGING) DB->Debug_Print("Engine reports PS3", clBlack);
        	} else {
	        EngineData->Cells[ENG_PS][Engine] = PS2;
            if (DEBUGGING) DB->Debug_Print("Engine reports PS2", clBlack);
    	    }
        } else {
        if (EngineData->Cells[ENG_PS][Engine] == PS2) {
	        if (DEBUGGING) DB->Debug_Print("Engine previously reported PS2", clBlack);
        	} else
            if (EngineData->Cells[ENG_PS][Engine] == PS3) {
				if (DEBUGGING) DB->Debug_Print("Engine previously reported PS3", clBlack);
            	} else
                if (DEBUGGING) DB->Debug_Print("Engine unknown type", clBlack);
		}
    //
    // Setup volume control defaults before startup occurs
    //
	if (!AlrdyStrted && (Engine != 0) && (Engine < LASHUPMIN) && (EngineData->Cells[ENG_NAME][Engine] != "*")) {   // skip if EngineData not initialized
      try {
          // Set Engine volumes.
          // Needed because Horn & Bell Sounds are muted in a LashUp and I don't seem to restore them correctly
          //
          char str[15];
		  snprintf(str, sizeof str, "v0%03d", EngineData->Cells[VOL_MASTER][Engine].ToIntDef(100)); 	// Master Volume
		  (void) ptrTIUCommThread->Send_String_FIFO(Engine, str, TIU, RemoteNo, 11, SHORT_WAIT, RECORD_OK);
		  //ptrOp[Engine]->LastSentMasterVolumeValue = EngineData->Cells[VOL_MASTER][Engine].ToInt();

		  snprintf(str, sizeof str, "v1%03d", EngineData->Cells[VOL_ENGINE][Engine].ToIntDef(100)); 	// Engine Sounds
		  (void) ptrTIUCommThread->Send_String_FIFO(Engine, str, TIU, RemoteNo, 11, SHORT_WAIT, RECORD_OK);

		  snprintf(str, sizeof str, "v2%03d", EngineData->Cells[VOL_ACCENT][Engine].ToIntDef(100)); 	// Accent Sounds
		  (void) ptrTIUCommThread->Send_String_FIFO(Engine, str, TIU, RemoteNo, 11, SHORT_WAIT, RECORD_OK);

		  snprintf(str, sizeof str, "v3%03d", EngineData->Cells[VOL_HORN][Engine].ToIntDef(100)); 	// Horn Sounds
		  (void) ptrTIUCommThread->Send_String_FIFO(Engine, str, TIU, RemoteNo, 11, SHORT_WAIT, RECORD_OK);

   		  snprintf(str, sizeof str, "v4%03d", EngineData->Cells[VOL_BELL][Engine].ToIntDef(100));	// Bell Sounds
		  (void) ptrTIUCommThread->Send_String_FIFO(Engine, str, TIU, RemoteNo, 11, SHORT_WAIT, RECORD_OK);
	      }
        catch ( EConvertError & oops ){
          // first time this Engine is used, the EngineData StringGrid is empty and the ToInt() function
          // returns an EConvertError exception
          DebugMessagePopup("Warning", "Empty Engine Data Field - Repaired", "");
          CaughtErrors->Value ++;
	      }
        catch (...) {
          CaughtErrors->Value ++;
          DebugMessagePopup("Error", "Unknown Startup Problem in Volume Settings", "");
          }
		}
    //
	// Startup    // Command "u4" for normal, "u6" for Extended
	// if either SHIFT key is pressed whilst clicking "Startup" button, use the extended startup sound
    // If the high-order bit is 1, the key is down; otherwise, it is up. If the low-order bit is 1, the key is toggled.
    // (HO bit means a mask value of 0x0080
	if (!AlrdyStrted) {
			// key pressed =  0xFF8X
			// key not pressed = 0x000X
			// "X" can be 0 or 1 - indicated key was toggled
		if (Setup->ExtendedSUSD->Checked || (ShiftKeyState < 0)) {
			(void)ptrTIUCommThread->Send_String_FIFO(Engine, COMMAND_STARTUP_EXTENDED, TIU, RemoteNo, 11, LONG_WAIT, RECORD_OK);    // in EngineStartUp()
		    } else {
			(void)ptrTIUCommThread->Send_String_FIFO(Engine, COMMAND_STARTUP, TIU, RemoteNo, 11, LONG_WAIT, RECORD_OK);    // in EngineStartUp()
		    }
        //---------------------------------------------------------------------------
		// TODO : "d0" and "s0" often fail right after startup when engine is first powered on - I added a short delay
        //---------------------------------------------------------------------------
#if USED0ANDS0AFTERSTARTUP
        Delay(WAITAFTERSTARTUP);
		// set DIR forward  "d0"
		(void)ptrTIUCommThread->Send_String_FIFO(Engine, DIRECTION_FORWARD, TIU, RemoteNo, 11, SHORT_WAIT, RECORD_OK);
		// set speed to zero unless it already is
		if (ptrOp[Engine]->SpeedSlider->Value >= 1.0) {
		    // BUG workaround - slider starts out at 10 	// causes the 's0' command to be sent because the slider is not at zero
			// This bug is now taken care of in the Op_Control.cpp file
			ptrOp[Engine]->SetSpeedSliderValue(0.0);
			}
#else
        Delay(WAITAFTERSTARTUP);
#endif
		// Startup Complete
        StartedConsists->Value++;   // in EngineStartUp()
		}
	//
	// if AlrdyStrted is true, read up the goal speed in the engine RAM 0x62-0x63
	// and set the speed to that value. This will correctly set the speed slider on the
	// operations window.
	if (AlrdyStrted && (Engine != 0) && (Engine <= ALLENGINE) && (EngineData->Cells[ENG_NAME][Engine] != "*")) {
		unsigned short iSpeed ;
		bool bretv = FetchWord(0x62, &iSpeed , Engine, TIU);    // Engine Speed * 8 (goal not instantaneous)-- returned by the TIU
        if (!bretv)
            iSpeed = 0;             // default
            else
    		iSpeed = iSpeed >> 3;	// divide by 8
		// disconnect from event OnValueChange
		SpeedSliderOnValueChange = ptrOp[Engine]->SpeedSlider->OnValueChange;
		ptrOp[Engine]->SpeedSlider->OnValueChange = NULL;
		ptrOp[Engine]->SpeedSlider->Value = iSpeed;
		ptrOp[Engine]->Set_Speed(iSpeed);	//  sends a speed command to the engine for the speed at which it is currently running
		// reconnect to event OnValueChange
		ptrOp[Engine]->SpeedSlider->OnValueChange = SpeedSliderOnValueChange;
        }
    //
	// setup control defaults on Operations form after startup occurs
    //
    if (!AlrdyStrted && (Engine != 0) && (Engine <= ALLENGINE) && (EngineData->Cells[ENG_NAME][Engine] != "*")) {   // skip if EngineData not initialized
      try {
          // this code causes these three command to be sent:
          //	Dq Da1 Dd1
          //
          if (Setup->StandardAccDec->Checked) { // on Engine Startup
          	// set Acc and Dec values to 1 in all modes of operation
        	ptrOp[Engine]->SetStandardAccDecValues(Engine, TIU);   // On Engine Startup
          	} else {
	      	if (Engine != ALLENGINE) {  // single Engine mode
		  	  ptrOp[Engine]->AccSpin->Enabled = true;
	      	  ptrOp[Engine]->DecSpin->Enabled = true;
			  // read Acc/Dec rates from engine
	    	  int iAcc, iDec;	// routine returns default values if we cannot communicate.
    		  (void) GetAccDecValues(Engine, TIU, &iAcc, &iDec);    // on Engine Startup
        	  SetNumSpin(ptrOp[Engine]->AccSpin, iAcc);		// Sends Acc Rate to the Engine if it changed
        	  SetNumSpin(ptrOp[Engine]->DecSpin, iDec);		// Sends Dec Rate to the Engine if it changed
			  } else {
              // All Engine Mode
			  // disable Acc and Dec controls
	    	  ptrOp[Engine]->AccSpin->Enabled = false;
	    	  ptrOp[Engine]->DecSpin->Enabled = false;
			  }
            }
          // Engine Type - this MUST be done first before the other switches are set
          SetComboBox(ptrOp[Engine]->EngineTypeComboBox, EngineData->Cells[ENG_TYPE][Engine].ToInt());
       	  // a lot of this can be removed if I can figure out how to ask the engine
          // what settings are stored in it.
          // According to Barry's book, the following are stored in the Engine:
          //   DCS Engine #
          //   Lighting including headlight, marker lights, interior lights, number board lights, ditch lights, and beacon
          //   Sounds appropriate to the engine
          //   Sound levels
          //   Chuff Rate and Proto Chuff setting
          //   Smoke level
          //   Accelerations and Deceleration rates
          //   original soft key settings
          //   custom name
          //   Chronometer, odometer, measured route, and battery condition
          // 5 LED Switches
          // ENG_HDLT contains five encoded values - Headlight State, Rule 17, Bell on with horn, Auto Bell on Startup and
          // Auto Whistle/Horn on Startup
          // RAM 0x25 bit 0
          SetSwitch(ptrOp[Engine]->HDLTSwitch, EngineData->Cells[ENG_HDLT][Engine].ToInt() & 0x01);
          SetLED(ptrOp[Engine]->SimulatedRule17LED, (EngineData->Cells[ENG_HDLT][Engine].ToInt() >> 1) & 0x01);
          SetLED(ptrOp[Engine]->BellOnWithHornLED, (EngineData->Cells[ENG_HDLT][Engine].ToInt() >> 2) & 0x01);
          SetLED(ptrOp[Engine]->AutoBellLED, (EngineData->Cells[ENG_HDLT][Engine].ToInt() >> 3) & 0x01);
          SetLED(ptrOp[Engine]->AutoWhistleLED, (EngineData->Cells[ENG_HDLT][Engine].ToInt() >> 4) & 0x01);
          //  Smoke - off first then setting
          // RAM 0x25 bit 7
          SetSwitch(ptrOp[Engine]->SmokeSwitch, true);
          SetSwitch(ptrOp[Engine]->SmokeSwitch, false);	// start with smoke/exhaust off
          // set set to saved value unless Smoke/Exhaust is forced OFF at startup
          if (!Setup->SmokeOFFatStartup->Checked) SetSwitch(ptrOp[Engine]->SmokeSwitch, EngineData->Cells[ENG_SMOKE][Engine].ToInt());
          // RAM 0x25 bit 3
          SetSwitch(ptrOp[Engine]->ClickClackSwitch, EngineData->Cells[ENG_FCC][Engine].ToInt());
          // ENG_SMOKELEVEL contains two encoded values - SmokeGroup (2 bits) and AutoLaboredSmokeSwitch (1 bit)
          // RAM 0x24 bits 1,0
          SetRadioGroup(ptrOp[Engine]->SmokeGroup, EngineData->Cells[ENG_SMOKELEVEL][Engine].ToInt() & 0x03);
          SetLED(ptrOp[Engine]->AutoLaboredSmokeLED, (EngineData->Cells[ENG_SMOKELEVEL][Engine].ToInt() >> 2) & 0x01);
          //
          // RAM 0x24 bit 2
          SetSwitch(ptrOp[Engine]->CabChatSwitch, EngineData->Cells[ENG_CHAT][Engine].ToInt());
          // RAM 0x24 bit 6
          SetSwitch(ptrOp[Engine]->ProtoChuffSwitch, EngineData->Cells[ENG_PROTOCHUFF][Engine].ToInt());
		  // the 'g' command does different things for diesels than for steam engines - handled inside of ChuffRateSwitchChanged()
          // ie - ChuffRateSwitch() does nothing if this is a diesel engine.
          SetSwitch(ptrOp[Engine]->ChuffRateSwitch, EngineData->Cells[ENG_CHUFFRATE][Engine].ToInt());
          // RAM 0x24 bit 4
          SetSwitch(ptrOp[Engine]->BrakesSwitch, EngineData->Cells[ENG_BRAKES][Engine].ToInt());
          // for ENG_TYPE, see above
          int mx = EngineData->Cells[ENG_MAXSPEED][Engine].ToIntDef(60);
          if (mx < 10) mx = Setup->MaxSpeed->Value; 	// maximum value
          ptrOp[Engine]->MaxSpeedSpin->Value = mx;
          //ptrOp[Engine]->MaxSpeedSpin->MaxValue = mx;  // bug found by Carl Ross - replace with next line
          ptrOp[Engine]->MaxSpeedSpin->MaxValue = Setup->MaxSpeed->Value;
          ptrOp[Engine]->SpeedSlider->SignalSettings->DigitalTo = ptrOp[Engine]->MaxSpeedSpin->Value;
		  ptrOp[Engine]->SpeedSlider->SignalSettings->ValueTo = ptrOp[Engine]->MaxSpeedSpin->Value;
		  ptrOp[Engine]->SpeedSlider->ScaleSettings->Steps = ptrOp[Engine]->MaxSpeedSpin->Value / 10;
          //
          if (EngineData->Cells[ENG_PS][Engine].ToIntDef(PS2) == PS2) ptrOp[Engine]->SetEnginePS2->Checked = true;
          if (EngineData->Cells[ENG_PS][Engine].ToIntDef(PS2) == PS3) ptrOp[Engine]->SetEnginePS3->Checked = true;
	      }
        catch ( EConvertError & oops ){
          // first time this Engine is used, the EngineData StringGrid is empty and the ToInt() function
          // returns an EConvertError exception
          DebugMessagePopup("Warning", "Empty Engine Data Field - Repaired", "");
          CaughtErrors->Value ++;
	      }
        catch (...) {
          CaughtErrors->Value ++;
          DebugMessagePopup("Error", "Unknown Startup Problem in Engine Data Settings", "");
          }
		}

	} else {    // it's already created, just show it
	ptrOp[Engine]->Show();
	}
//
// if either CTRL key is pressed whilst clicking "Startup" button, show the Sound form also
if (Setup->AutoSoundCtrls->Checked || (CtrlKeyState < 0)) {
	SoundCtrlsButton->OnClick(SoundCtrlsButton);
    }
if (iDelayTimeSeconds > 0) Delay(iDelayTimeSeconds * 1000);	// Delay Time in Seconds convert to Delay time in microseconds
Screen->Cursor = OldCursor;		// restore cursor
// Note: Engine startup usually continues for several seconds after this routine
// finished. If you click on another button before the engine is ready for a
// a command, the engine seems to just ignore the command.
return;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::StartupActiveEngines1Click(TObject *Sender)
{
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }

StartupAllActiveEngines(5);	// Wait X seconds between each engine startup
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::StartupAllActiveEngines(int iDelayTimeSeconds)
{
TMessagePopup *MessagePopup = NULL;  // StartupAllActiveEngines
const TCursor OldCursor = Screen->Cursor; 	// save cursor
Screen->Cursor = crHourGlass;
Application->ProcessMessages(); // process all messages before the popup
/* ChatGPT recommended this method. It does not work in BCB4.0 but seems
to work in CB10.1
std::unique_ptr<TMessagePopup> MessagePopup(new TMessagePopup(this, Handle, NULL, NULL));
*/
if (!MessagePopup){
	MessagePopup = new TMessagePopup(this, Handle, NULL, NULL);     // Pass "this" as the owner
	assert(MessagePopup != NULL);		// make sure that construction worked
	}
MessagePopup->Show();
MessagePopup->MessageLine->Caption = "   Starting all Active Engines   ";
MessagePopup->StatusLine->Caption = "   Please Wait   ";
MessagePopup->Repaint();

DWORD OldEngineValue = EngineSpin->Value;

// Start All Active Engines - 1-99 (DCS 2-100)
// (total of 99 engines)
// NOTE: Only startup active engines that have not yet been started
for (int iEng = 1; iEng <= ENGINEMAX ; iEng++) {
   	if ((EngineData->Cells[ENG_NAME][iEng] != "*") && (EngineData->Cells[ENG_STATE][iEng] == ACTIVE) && (ptrOp[iEng] == NULL)) {
    	EngineSpin->Value = iEng; 	// show the engine in the Engine List and Engine Number Spinner
        // TIUNo of selected Engine
        int MyTIUNo = EngineData->Cells [TIU_NUMBER] [iEng].ToIntDef(0);
        if (MyTIUNo == 0) {
            continue;  // Quietly skip engine if TIU number is not known
            }
        MessagePopup->StatusLine->Caption = "   Engine # " + String(iEng) + "   ";
		MessagePopup->Repaint();
		EngineStartUp(iEng, MyTIUNo, false, 0);	// Startup Engine: send startup commands and switch setup and open Operations window
        if (iDelayTimeSeconds > 0) Delay(iDelayTimeSeconds * 1000);	// wait iDelayTimeSeconds seconds after each startup for startup sequence to run
		}
    }
EngineSpin->Value = OldEngineValue;
Screen->Cursor = OldCursor;		// restore cursor

if (MessagePopup != NULL) {
    MessagePopup->Hide();
    delete MessagePopup;
    MessagePopup = NULL;
    }

//DebugMessage("Startup All Active Engines Complete");
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::StartupActiveEngines2Click(TObject *Sender)
{
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }

// Run Startup All
StartupActiveEngines1->OnClick(StartupActiveEngines2);
}
//---------------------------------------------------------------------------
void __fastcall TMainForm::ShutdownAllEngines1Click(TObject *Sender)
{
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }

// Run Shutdown All with one second delay between the shutdown commands
ShutdownAllEngines(1);
}

//---------------------------------------------------------------------------
void __fastcall TMainForm::ShutdownAllEngines(int iDelayTimeSeconds)
{
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }

TMessagePopup *MessagePopup = NULL;  // ShutdownAllEngines
TCursor OldCursor = Screen->Cursor; 	// save cursor
Screen->Cursor = crHourGlass;
Application->ProcessMessages(); // process all messages before the popup
if (!MessagePopup){
	MessagePopup = new TMessagePopup(this, Handle, NULL, NULL);     // Pass "this" as the owner
	assert(MessagePopup != NULL);		// make sure that construction worked
	}
MessagePopup->Show();
MessagePopup->MessageLine->Caption = "   Shutting Down All Engines   ";
MessagePopup->StatusLine->Caption = "   Please Wait   ";
MessagePopup->Repaint();

DWORD OldEngineValue = EngineSpin->Value;
// Send Shutdown command to all active engines in the engine list
for (int iEng = 1; iEng <= ENGINEMAX ; iEng++) {    // Engines 1 to 99
    if (ptrOp[iEng] != NULL) {
	    // if the engine is running (that is, the operations window is up)
	    // Shutdown
    	EngineSpin->Value = iEng;
        // TIUNo of selected Engine
        int MyTIUNo = EngineData->Cells [TIU_NUMBER] [iEng].ToIntDef(0);
        if (MyTIUNo == 0) {
            continue; // Quietly skip engine if TIU number is not known
            }
        MessagePopup->StatusLine->Caption = "   Engine # " + String(iEng) + "   ";
		MessagePopup->Repaint();
		ConsistShutDown(iEng, MyTIUNo, NULL, 0);	// Shutdown Engine and close Operations Window
        if (iDelayTimeSeconds > 0) Delay(iDelayTimeSeconds * 1000);	// wait iDelayTimeSeconds seconds after each shutdown for shutdown sequence to run
        } else {
        // Send Shutdown command directly to all active engines in the engine list
       	if (EngineData->Cells[ENG_NAME][iEng] != "*" && (EngineData->Cells[ENG_STATE][iEng] == ACTIVE)) {
            // TIUNo of selected Engine
            int MyTIUNo = EngineData->Cells [TIU_NUMBER] [iEng].ToIntDef(0);
            if (MyTIUNo == 0) {
                continue; // Quietly skip engine if TIU number is not known
                }
            MessagePopup->StatusLine->Caption = "   Engine # " + String(iEng) + "   ";
    		MessagePopup->Repaint();
            // NOTE: the TIU will NAK each shutdown command if the engine is not actually on the layout
			(void) ptrTIUCommThread->Send_String_FIFO(iEng, "u5", MyTIUNo, RemoteNo,  11, SHORT_WAIT, RECORD_OK);	// ShutdownAllEngines()
            if (iDelayTimeSeconds > 0) Delay(iDelayTimeSeconds * 1000);	// wait iDelayTimeSeconds seconds after each shutdown for shutdown sequence to run
           	}
        }
    }
EngineSpin->Value = OldEngineValue;
Screen->Cursor = OldCursor;		// restore cursor

if (MessagePopup != NULL) {
    MessagePopup->Hide();
    delete MessagePopup;
    MessagePopup = NULL;
    }

//DebugMessage("Shutdown All Engines Complete");
}
//---------------------------------------------------------------------------
void __fastcall TMainForm::ShutDownActiveEngines(int iDelayTimeSeconds)
{
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
TMessagePopup *MessagePopup = NULL;  // ShutDownActiveEngines
TCursor OldCursor = Screen->Cursor; 	// save cursor
Screen->Cursor = crHourGlass;
Application->ProcessMessages(); // process all messages before the popup
if (!MessagePopup){
	MessagePopup = new TMessagePopup(this, Handle, NULL, NULL);     // Pass "this" as the owner
	assert(MessagePopup != NULL);		// make sure that construction worked
	}
MessagePopup->Show();
MessagePopup->MessageLine->Caption = "   Shutting Down Active Engines   ";
MessagePopup->StatusLine->Caption = "   Please Wait   ";
MessagePopup->Repaint();

DWORD OldEngineValue = EngineSpin->Value;
// Send Shutdown command to all engines in the engine list (running or active or not)
for (int iEng = 1; iEng <= ENGINEMAX ; iEng++) {    // Engines 1 to 99
    if (ptrOp[iEng] != NULL) {
	    // if the engine is running, use the speed scroller
	    // Shutdown
    	EngineSpin->Value = iEng;
        // TIUNo of selected Engine
        int MyTIUNo = EngineData->Cells [TIU_NUMBER] [iEng].ToIntDef(0);
        if (MyTIUNo == 0) {
            continue; // Quietly skip engine if TIU number is not known
            }
        MessagePopup->StatusLine->Caption = "   Engine # " + String(iEng) + "   ";
		MessagePopup->Repaint();
		ConsistShutDown(iEng, MyTIUNo, NULL, 0);	// Shutdown Engine and close Operations Window
        if (iDelayTimeSeconds > 0) Delay(iDelayTimeSeconds * 1000);	// wait iDelayTimeSeconds seconds after each shutdown for shutdown sequence to run
        }
    }
EngineSpin->Value = OldEngineValue;
Screen->Cursor = OldCursor;		// restore cursor

if (MessagePopup != NULL) {
    MessagePopup->Hide();
    delete MessagePopup;
    MessagePopup = NULL;
    }

//DebugMessage("Shutdown Active Engines Complete");
}
//---------------------------------------------------------------------------
void __fastcall TMainForm::ShutdownButtonClick(TObject *Sender)
{
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
    return;
    }
if (EngineNo == ALLENGINE) {
    // in AllEngineMode, send the Shutdown commands to all engines on all TIU (MyTIUNo is not used)
    MyTIUNo = TIUSpin->ValueAsInt;
    } else {
    // TIUNo of selected Engine
    MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineData->Row].ToIntDef(0);
    if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
        DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineData->Row));
        // fail if the TIU number is not set by [Read] button
        return;
        }
    if (!TIUSelection[MyTIUNo - 1]) {
        DebugMessagePopup("Warning", "TIU #" + String(MyTIUNo) + " associated with this engine is not active", "");
        return;
        }
    }

AltKeyState = ShutdownButton->Focused() ? GetKeyState(VK_MENU) : 0;		// Run Shutdown All
ShiftKeyState = ShutdownButton->Focused() ? GetKeyState(VK_SHIFT) : 0;	// Use Extended Shutdown
//CtrlKeyState = ShutdownButton->Focused() ? GetKeyState(VK_CONTROL) : 0;	// Not used yet

// key pressed =  0xFF8X
// key not pressed = 0x000X
// "X" can be 0 or 1 - indicated key was toggled
// if either ALT key is pressed whilst clicking "Shutdown" button, run "Shutdown All"
if (AltKeyState < 0) {
	ShutdownAllEngines1->OnClick(ShutdownButton);
	AltKeyState = ShiftKeyState = CtrlKeyState = 0;
    return;
    }
// You can shutdown a consist even it was not started up
ConsistShutDown(EngineNo, MyTIUNo, NULL);
AltKeyState = ShiftKeyState = CtrlKeyState = 0;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CMLashUpShutdown(TMessage &Message)
{
// process shutdown Lashup message - 101-120
//
if (Message.WParam == NULL) {   // Engine List must have been created, otherwise fail.
    return;
    }
unsigned char LashUpEngineList[LASHUP_ENGINE_LIST_LENGTH];	// Bytes containing the LashUp in the form 0x2C 0x01 0x82 0x03 0xFF
(void) strncpy((char *)LashUpEngineList, (char *) Message.WParam, sizeof LashUpEngineList); 	// binary engine list from Word parameter

const int LUNo = Message.LParam & 0x00FF;	// set Lashup Number from Long Parameter -  101-120
if (LUNo == 0) return;						// no LashUp Number
assert(LUNo >= LASHUPMIN && LUNo <= LASHUPMAX);	// value 101 to 120 - DCS Engine Number always 102 (0x66)
const int TIU = (Message.LParam & 0xFF00) >> 8;	// set TIU Number from Long Parameter - TIU Number 1-5
assert(TIU >= 1 && TIU <= TIU);
// You can shutdown a lashup even if it was not started up - needs the engine list to be created
ConsistShutDown(LUNo, TIU, LashUpEngineList);
AltKeyState = ShiftKeyState = CtrlKeyState = 0;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CMShutDown(TMessage &Message)
{
// process Engine Shutdown message - Engines 0-99 and All Engine Op
const int TIU = Message.WParam;		// set TIU Number from Word Parameter
const int Consist = Message.LParam;	// set Consist Number from Long Parameter
assert (TIU >= 1 && TIU <= TIUMAX);
assert (Consist >= (Setup->AllowZero->Checked? FACTORYRESET : ENGINEMIN));	// 0 factory reset, 1-99, 100 "all engine operation

// You can shutdown a consist even if it was not started up
ConsistShutDown(Consist, TIU, NULL);
AltKeyState = ShiftKeyState = CtrlKeyState = 0;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ConsistShutDown(int Consist, int TIU, char * LashUpEngineList, int iDelayTimeSeconds)
{
// shutdown engine/lashup given by Consist on TIU
// handles Engines & LashUps from 0 to 120
assert(Consist >= (Setup->AllowZero->Checked? FACTORYRESET : ENGINEMIN) && Consist <= LASHUPMAX);	// 0 factory reset, 1-99, 100 "all engine operation, 101-120 LashUps
assert (TIU >= 1 && TIU <= TIUMAX);
// if TIU Port is not connected fail
if (!IsTIUConnected()) return;
//
// You can shutdown a consist/Lashup even it was not started up
//
// Engine List must have been created
if (Consist >= LASHUPMIN && LashUpEngineList == NULL){
    return; // fail
    }

const TCursor OldCursor = Screen->Cursor; 	// save cursor
Screen->Cursor = crHourGlass;
//
// Check for All Engine Mode
//
if (Consist == ALLENGINE && IsTIUConnected()) {
    //
    if (Setup != NULL && Setup->Radio_COM_Mode && Setup->TXOnlyComm->Checked) {	// cannot do this function without full duplex communications
        DebugMessagePopup("Warning", MainForm->TXMsg, "");
        Screen->Cursor = OldCursor;		// restore cursor
        return;
        }

    // updated for All Engine Mode using multiple TIU
    // when sending a command to each TIU,
    // presend the J13HexString
    //
    J13HexString = CreateJ13HexString();
    ptrTIUCommThread->Send_String_FIFO(Consist, J13HexString.c_str(), TIU, RemoteNo, 11, SHORT_WAIT, RECORD_OK);

    }
// make sure that we are in command mode if we are shutting down an engine - this is really not needed
#ifdef NOTNEEDED
if (Consist < LASHUPMIN) {
	if (!ptrTIUCommThread->Send_String_FIFO(Consist, "m4", TIU, RemoteNo, 11, SHORT_WAIT, RECORD_OK, LashUpEngineList)) {   // was ptrLUEngineList[Consist]
	    Screen->Cursor = OldCursor;
		//new TMessageDisplayThread(false, this, Handle, "Error", "Engine " + String(Consist) + " not on track (Consist Shutdown)", "", UIPOPUPTIMEOUT);
        DebugMessagePopup("Error", "Engine " + String(Consist) + " not on track (Consist Shutdown)");
        // TODO : This leaves the Op window on the screen, maybe we should take it down
		return; // shutdown failed
    	}
    }
#endif
// set speed to zero
if (!ptrTIUCommThread->Send_String_FIFO(Consist, "s0", TIU, RemoteNo, 11, SHORT_WAIT, RECORD_OK, LashUpEngineList)) {
    Screen->Cursor = OldCursor;
	//new TMessageDisplayThread(false, this, Handle, "Error", "Engine/LashUp " + String(Consist) + " not on track (Consist Shutdown)", "", UIPOPUPTIMEOUT);
    DebugMessagePopup("Error", "Engine/LashUp " + String(Consist) + " not on track (Consist Shutdown)");
	return; // shutdown failed
    // TODO : This leaves the Op window on the screen, maybe we should take it down
	}
// Shutdown   // Command "u5" for normal, "u7" for extended
// if either SHIFT key is pressed whilst clicking "Shutdown" button, use the extended shutdown sound
if (Setup->ExtendedSUSD->Checked || (ShiftKeyState < 0)) {
	(void)ptrTIUCommThread->Send_String_FIFO(Consist, "u7", TIU, RemoteNo, 11, SHORT_WAIT, RECORD_OK, LashUpEngineList);
    } else {
	(void)ptrTIUCommThread->Send_String_FIFO(Consist, "u5", TIU, RemoteNo, 11, SHORT_WAIT, RECORD_OK, LashUpEngineList); // //ConsistShutDown()
    }
// if SPW is enabled, make sure that it is turned off
if (ptrOp[Consist] != NULL) {
	if (ptrOp[Consist]->ProtoWhistleSwitch->Checked) {
        ptrOp[Consist]->ProtoWhistleSwitch->Checked = false;
        }
	}
// Shutdown Complete
if (StartedConsists->Value > 0) StartedConsists->Value--;   // in ConsistShutDown() for both engines, all engines and lashups
//
// Close Volume Control window if it is open
// Save information in EngineData and clear the pointer to NULL
if (ptrVolume[Consist] != NULL) {
	if ((Consist != 0) && (Consist < LASHUPMIN) && (EngineData->Cells[ENG_NAME][Consist] != "*")) {	// there must be an Engine Name in [ENG_NAME]
        // error in next line found by Roger Johns, 24 Mar 24
        assert(Consist >= 1 && Consist < LASHUPMIN);	// 1-99, 100 "all engine operation"
		// Engine Sound on/off is never saved, volume is restored before engine shutdown
        if (ptrVolume[Consist]->EngineSoundOnSwitch->Checked == false)
	        ptrVolume[Consist]->EngineSoundOnSwitch->Checked = true; // restore the volume setting if necessary

		// Save information in EngineData for Engines 1-99 and 100 "all engine operation"  (0-100)
		EngineData->Cells[VOL_MASTER][Consist] = ptrVolume[Consist]->MasterVolume->Value;
		EngineData->Cells[VOL_ENGINE][Consist] = ptrVolume[Consist]->EngineVolume->Value;
		EngineData->Cells[VOL_ACCENT][Consist] = ptrVolume[Consist]->AccentVolume->Value;
		EngineData->Cells[VOL_HORN][Consist] = ptrVolume[Consist]->HornWhistleVolume->Value;
		EngineData->Cells[VOL_BELL][Consist] = ptrVolume[Consist]->BellVolume->Value;

		EngineData->Cells[SPARE1][Consist] = 0;
		EngineData->Cells[SPARE2][Consist] = 0;
        }
	// Close the form
    assert(Consist >= (Setup->AllowZero->Checked? 0 : 1) && Consist <= LASHUPMAX);	// 0 factory reset, 1-99, 100 "all engine operation, 101-120 LashUps
    delete ptrVolume[Consist];
    ptrVolume[Consist] = NULL;
	}
// Close Operations Window if it is open
// Save information in EngineData and clear the pointer to NULL
// Save only if we are shutting down an engine
if (ptrOp[Consist] != NULL) {
	if ((Consist != 0) && (Consist < LASHUPMIN) && (EngineData->Cells[ENG_NAME][Consist] != "*")) {	// there must be an Engine Name in [ENG_NAME]
		// Save information in EngineData, Engines 1-99 and "all engine operation"  (0-100)
        assert(Consist >= 1 && Consist <= ALLENGINE);	// 1-100
        // ENG_HDLT contains five encoded values - Headlight State, Simulated Rule 17,  Bell on with horn, Auto Bell and Auto Whistle
		EngineData->Cells[ENG_HDLT][Consist] = (short)ptrOp[Consist]->HDLTSwitch->Checked |
        		(ptrOp[Consist]->SimulatedRule17LED->Checked << 1) | (ptrOp[Consist]->BellOnWithHornLED->Checked << 2) |
                (ptrOp[Consist]->AutoBellLED->Checked << 3) | (ptrOp[Consist]->AutoWhistleLED->Checked << 4);
		EngineData->Cells[ENG_SMOKE][Consist] = (short)ptrOp[Consist]->SmokeSwitch->Checked;
		EngineData->Cells[ENG_FCC][Consist] = (short)ptrOp[Consist]->ClickClackSwitch->Checked;
        // ENG_SMOKELEVEL contains two encoded values - SmokeGroup (2 bits) and Auto Labored Smoke (1 bit)
		EngineData->Cells[ENG_SMOKELEVEL][Consist] = ptrOp[Consist]->SmokeGroup->ItemIndex |
        		(ptrOp[Consist]->AutoLaboredSmokeLED->Checked << 2);
   		EngineData->Cells[ENG_CHAT][Consist] = (short)ptrOp[Consist]->CabChatSwitch->Checked;
   		EngineData->Cells[ENG_PROTOCHUFF][Consist] = (short)ptrOp[Consist]->ProtoChuffSwitch->Checked;
   		EngineData->Cells[ENG_CHUFFRATE][Consist] = (short)ptrOp[Consist]->ChuffRateSwitch->Checked;
   		EngineData->Cells[ENG_BRAKES][Consist] = (short)ptrOp[Consist]->BrakesSwitch->Checked;
   		EngineData->Cells[ENG_DITCH][Consist] = ptrOp[Consist]->DitchComboBox->ItemIndex;
   		EngineData->Cells[ENG_BEACON][Consist] = (short)ptrOp[Consist]->BeaconLightSwitch->Checked;
   		EngineData->Cells[ENG_AUX1][Consist] = (short)ptrOp[Consist]->ab3ab2Switch->Checked;
   		EngineData->Cells[ENG_AUX2][Consist] = (short)ptrOp[Consist]->ab5ab4Switch->Checked;
    	EngineData->Cells[ENG_AUX3][Consist] = (short)ptrOp[Consist]->ab7ab6Switch->Checked;
    	EngineData->Cells[ENG_MARKER][Consist] = (short)ptrOp[Consist]->MarkerLightSwitch->Checked;
   		EngineData->Cells[ENG_TYPE][Consist] = (short)ptrOp[Consist]->EngineTypeComboBox->ItemIndex;	// Steam or Diesel or Electric
   		EngineData->Cells[ENG_MAXSPEED][Consist] = ptrOp[Consist]->MaxSpeedSpin->Value;	// Max speed for this engine
   		// EngineData->Cells[ENG_STATE][Consist] = ACTIVE;	// don't change ENG_STATE - only changed by READ pushbutton
        													// and LashUp create and breakup and manually from the Engine popup menu
   		// EngineData->Cells[TIU_NUMBER][Consist] = 1;	// don't change TIU_NUMBER - only changed by READ pushbutton
		// EngineData->Cells[ENG_PS][Consist] = 0;		// don't change ENG_PS - only changed by Engine Startup and Operations popup Menu
        }
	// Close the form
    delete ptrOp[Consist];
    ptrOp[Consist] = NULL;
    if (Consist >= LASHUPMIN) { 		// if a LashUp, delete the LUEngineList and LUNameList entry if they exist
        if (ptrLUNameList[Consist]) {
            delete ptrLUNameList[Consist];
            ptrLUNameList[Consist] = NULL;
            }
        if (ptrLUEngineList[Consist]) {
            delete ptrLUEngineList[Consist];
            ptrLUEngineList[Consist] = NULL;
            }
        }
    }

if (Consist == EngineSpin->ValueAsInt) {
	SoundCtrlsButton->Enabled = false;	// disable Volume Button
	OpButton->Enabled = false;	// disable Operations Button
	}
//
// Redraw the Engine List or the LashUp List
if (Consist <= ALLENGINE) {
	EngineData->Repaint();
    } else {
    LashUp->LashUpData->Repaint();
    }
//
if (iDelayTimeSeconds > 0) Delay(iDelayTimeSeconds * 1000);	// Delay Time in Seconds convert to Delay time in microseconds
Screen->Cursor = OldCursor;
return;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CMOpControl(TMessage &Message)
{
// process Engine Open Operations Control message - Engines 0-100  (LashUps are handled by another message)
const int TIU = Message.WParam;		// set TIU Number from Word Parameter
const int Consist = Message.LParam;	// set Consist Number from Long Parameter
assert (TIU >= 1 && TIU <= TIUMAX);
assert (Consist >= (Setup->AllowZero->Checked? 0 : 1) && Consist <= 100);	// 0 factory reset, 1-99, 100 "all engine operation
assert(ptrOp[Consist] != NULL);	// not running - caller must validate
									   	// that the consist is running
EngineStartUp(Consist, TIU, true, 0);	// engine is already started so don't send actual startup commands
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CMRadioCOMStatus(TMessage &Message)
{
// Thread to VCL interface - Handles all VCL operations for the Radio COM
const uint32 event = Message.LParam;	// set event from Long Parameter
const uint32 DoneReason = Message.WParam;	// Reason for Disconnect
String Msg;

switch(event) {
    case  SERIAL_CONNECTED  :
        Msg = "Connected to " + ConnectedWiFiRadioLED->Caption;
        if (DEBUGGING) DB->Debug_Print(Msg);
        //StatusBar->Panels->Items[RADIO_PORT_STATUS]->Text = "Radio Serial Connected";
        ConnectedWiFiRadioLED->Checked = true;
        ConnectedWiFiRadioLED->Flashing = false;
        break;

    case  SERIAL_DISCONNECTED  :
        Msg = "Disconnected from " + ConnectedWiFiRadioLED->Caption + " (" + String(DoneReason) + ")";
        if (DEBUGGING) DB->Debug_Print(Msg);
        //StatusBar->Panels->Items[RADIO_PORT_STATUS]->Text = "Radio Serial Disconnected";
        ConnectedWiFiRadioLED->Checked = false;
        ConnectedWiFiRadioLED->Flashing = false;
        if (!DisconnectRequest) {
            // this is the case where tserial_event detected an error/failure of some sort and sent a SERIAL_DISCONNECTED event
			TIUSpin->ColorDefault = clYellow;	// set TIU box color to default indicating unknown comm with the TIU
			AIUSpin->ColorDefault = clYellow;
			if (DEBUGGING) DebugMessagePopup("Error", "TIU port unexpectedly disconnected (" + String(DoneReason) + ")");
            }
        break;

    case  SERIAL_DATA_SENT  :
        // Nothing needs to be done here
        break;

    case  SERIAL_RING_ON    :
        MainForm->Ring->Checked = true;
        break;

    case  SERIAL_RING_OFF   :
        MainForm->Ring->Checked = false;
        break;

    case  SERIAL_CD_ON      :
        MainForm->CD->Checked = true;
        CD_Count += 1;
        MainForm->CD_Counter->Text = String(CD_Count);
        break;

    case  SERIAL_CD_OFF     :
        MainForm->CD->Checked = false;
        break;

    case  SERIAL_CTS_ON		:
        MainForm->CTS->Checked = true;
        CTS_Count += 1;
        MainForm->CTS_Counter->Text = String(CTS_Count);
        break;

    case  SERIAL_CTS_OFF		:
        MainForm->CTS->Checked = false;
        break;

    case  SERIAL_DSR_ON		:
        MainForm->DSR->Checked = true;
        DSR_Count += 1;
        MainForm->DSR_Counter->Text = String(DSR_Count);
        break;

    case  SERIAL_DSR_OFF		:
        MainForm->DSR->Checked = false;
        break;

    case  SERIAL_ERR			:
        TIUPortCE = Message.WParam;
        MainForm->Err->Checked = true;
        MainForm->ExitButton->Enabled = true;  // allow exit on Serial Error
        break;
        /*  From winbase.h Values returned from ClearCommError()
        #define CE_RXOVER           0x0001  // Receive Queue overflow
        #define CE_OVERRUN          0x0002  // Receive Overrun Error
        #define CE_RXPARITY         0x0004  // Receive Parity Error
        #define CE_FRAME            0x0008  // Receive Framing error
        #define CE_BREAK            0x0010  // Break Detected
        #define CE_TXFULL           0x0100  // TX Queue is full
        #define CE_PTO              0x0200  // LPTx Timeout
        #define CE_IOE              0x0400  // LPTx I/O Error
        #define CE_DNS              0x0800  // LPTx Device not selected
        #define CE_OOP              0x1000  // LPTx Out-Of-Paper
        #define CE_MODE             0x8000  // Requested mode unsupported
        */
    case  SERIAL_BREAK			:
        MainForm->Break->Checked = true;
        break;
    }

}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CMRFIDCOMStatus(TMessage &Message)
{
// Thread to VCL interface - Handles all VCL operations for the RFID COM
const uint32 event = Message.LParam;		// set event from Long Parameter
const uint32 DoneReason = Message.WParam;	// disconnect reason from Word Parameter
String Msg;

switch(event) {
    case  SERIAL_CONNECTED  :
        Msg = "Connected to " + ConnectedRFIDLED->Caption;
        if (DEBUGGING) DB->Debug_Print(Msg);
        //StatusBar->Panels->Items[RFID_PORT_STATUS]->Text = "RFID Serial Connected";
        ConnectedRFIDLED->Checked = true;
        //ConnectedRFIDLED->Flashing = false;
		bRFIDCOMConnected = true;	// this is the only place this is set true
        bRFIDSendComplete = true;
        break;

    case  SERIAL_DISCONNECTED  :
        Msg = "Disconnected from " + ConnectedRFIDLED->Caption + " (" + String(DoneReason) + ")";
        if (DEBUGGING) DB->Debug_Print(Msg);
        //StatusBar->Panels->Items[RFID_PORT_STATUS]->Text = "RFID Serial Disconnected";
        if (!DisconnectRequest) {
            // this is the case where tserial_event detected an error/failure of some sort and sent a SERIAL_DISCONNECTED event
            if (DEBUGGING) DebugMessagePopup("Error", "RFID port unexpectedly disconnected (" + String(DoneReason) + ")");
            }
        ConnectedRFIDLED->Checked = false;
        //ConnectedRFIDLED->Flashing = false;
		bRFIDCOMConnected = false;	// this is the only place this is set false
        bRFIDSendComplete = true;                
        break;

    case  SERIAL_DATA_SENT  :
        bRFIDSendComplete = true;
        break;

    case  SERIAL_RING_ON    :
        assert(false);
        break;

    case  SERIAL_RING_OFF   :
        assert(false);
        break;

    case  SERIAL_CD_ON      :
        assert(false);
        break;

    case  SERIAL_CD_OFF     :
        assert(false);
        break;

    case  SERIAL_CTS_ON		:
        assert(false);
        break;

    case  SERIAL_CTS_OFF	:
        assert(false);
        break;

    case  SERIAL_DSR_ON		:
        assert(false);
        break;

    case  SERIAL_DSR_OFF	:
        assert(false);
        break;

    case  SERIAL_ERR		:
        RFIDPortCE = Message.WParam;
        assert(false);
        break;
        /*  From winbase.h Values returned from ClearCommError()
        #define CE_RXOVER           0x0001  // Receive Queue overflow
        #define CE_OVERRUN          0x0002  // Receive Overrun Error
        #define CE_RXPARITY         0x0004  // Receive Parity Error
        #define CE_FRAME            0x0008  // Receive Framing error
        #define CE_BREAK            0x0010  // Break Detected
        #define CE_TXFULL           0x0100  // TX Queue is full
        #define CE_PTO              0x0200  // LPTx Timeout
        #define CE_IOE              0x0400  // LPTx I/O Error
        #define CE_DNS              0x0800  // LPTx Device not selected
        #define CE_OOP              0x1000  // LPTx Out-Of-Paper
        #define CE_MODE             0x8000  // Requested mode unsupported
        */
    case  SERIAL_BREAK		:
        assert(false);
        break;
    }
}
//---------------------------------------------------------------------------
// these next routines setup the switches and other controls

void __fastcall TMainForm::SetSwitch(TAbRockerSwitch * Switch, int Checked)
{
assert(Checked == 1 || Checked == 0); 	// Check for actual true and false numeric values
if (Switch->Checked == Checked) {
	assert(Switch->OnStatusChanged != NULL);
	Switch->OnStatusChanged(Switch);
	} else {
	Switch->Checked = Checked;
	}
}

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

void __fastcall TMainForm::SetRadioGroup(TRadioGroup * RadioGroup, int Position)
{
assert(Position >= 0 && Position <= 12); 	// limit radiogroups to 13 positions
RadioGroup->ItemIndex = Position;
assert(RadioGroup->OnClick != NULL);
RadioGroup->OnClick(RadioGroup);
}
//--------------------------------

void __fastcall TMainForm::SetComboBox(TComboBox * ComboBox, int Position)
{
assert(Position >= -1 && Position <= 5); 	// limit combo boxes to 6 positions
if (Position == -1) Position = 0;
ComboBox->ItemIndex = Position;
assert(ComboBox->OnClick != NULL);
ComboBox->OnClick(ComboBox);
}
//--------------------------------

void __fastcall TMainForm::SetSlider(TAbVSlider * Slider, float Value)
{
assert(Value >= 0 && Value <= SPEEDMAX); 	// limit sliders to  max value of 120
if (Slider->Value == Value) {
	assert(Slider->OnValueChange != NULL);
	Slider->OnValueChange(Slider);
	} else {
	Slider->Value = Value;
	}
}
//--------------------------------

void __fastcall TMainForm::SetLED(TAbLED * LED, int Checked)
{
assert(Checked == 1 || Checked == 0);  	// Check for actual true and false numeric values
if (LED->Checked == Checked) {
	assert(LED->OnStatusChanged != NULL);
	LED->OnStatusChanged(LED);
	} else {
	LED->Checked = Checked;
	}
}
//--------------------------------

void __fastcall TMainForm::SetToggle(TAbToggleSwitch * Switch, int Checked)
{
assert(Checked == 1 || Checked == 0);  	// Check for actual true and false numeric values
if (Switch->Checked == Checked) {
	assert(Switch->OnClick != NULL);
	Switch->OnClick(Switch);
	} else {
	Switch->Checked = Checked;
	}
}
//--------------------------------
void __fastcall TMainForm::SetNumSpin(TAbNumSpin * Spinner, int Value)
{
assert(Value >= 0 && Value <= SPEEDMAX);		// limit spinners to max value of 120
if (Spinner->Value == Value) {
	assert(Spinner->OnValueChanged != NULL);
	Spinner->OnValueChanged(Spinner);
	} else {
	Spinner->Value = Value;
	}
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::OpButtonClick(TObject *Sender)
{
// Start the Operations form for engines 0-100
//
// https://stackoverflow.com/questions/31649315/c-builder-button-click-function-reentry
LOCKEM()

int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
assert(EngineNo >= (Setup->AllowZero->Checked? 0 : 1) && EngineNo <= ALLENGINE);	// 0 factory reset, 1-99, 100 "all engine operation"
if (!IsConsistRunning(EngineNo)) return;	// not running

// Manually create form and show it
// Many Operation Control Forms - if the form is not already created, then create it
//
if (ptrOp[EngineNo] == NULL) {	// EngineNo 0-100 (0 is factory reset, 1-99, 100 for "all engine operation")
	if (ptrLUEngineList[EngineNo] != NULL) {
    	delete[] ptrLUEngineList[EngineNo];	// if it's left over, delete the string - pointer to a char array
		ptrLUEngineList[EngineNo] = NULL;		// never a LashUpEngineList for a regular engine
        }
    int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
    if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
        MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
        return;
        }
	ptrOp[EngineNo] = new TOp(this, RemoteNo, EngineNo, MyTIUNo, EngineData->Cells[ENG_NAME][EngineNo], NULL);   // create the Form and show it
								// Pass "this" as the owner
    							// 1-99 Engine Number, 100 "all engine operaton"
	assert(ptrOp[EngineNo] != NULL);	// make sure that construction worked
    ptrOp[EngineNo]->ParentHandle = Handle; // for return messages
    //
	} else {    // it's already created
	ptrOp[EngineNo]->Show();
	}
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CMSoundControl(TMessage &Message)
{
// process Engine/LashUp Open Sound Control message - Engines 0-100
// LashUps have their own message : CM_LASHUPSOUNDCTRLS
const int TIU = Message.WParam;		// set TIU Number from Word Parameter
const int Consist = Message.LParam;	// set Consist Number from Long Parameter
assert (TIU >= 1 && TIU <= TIUMAX);
assert (Consist >= (Setup->AllowZero->Checked? 0 : 1) && Consist <= ALLENGINE);	// 0 factory reset, 1-99, and 100 "all engine operation
assert(ptrOp[Consist] != NULL);	// not running - caller must validate
									   	// that the consist is running
OpenSoundControl(Consist, TIU);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::SoundCtrlsButtonClick(TObject *Sender)
{
// Allow Engines or All Engines Only
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
assert(EngineNo >= (Setup->AllowZero->Checked? 0 : 1) && EngineNo <= ALLENGINE);	// 0 factory reset, 1-99, 100 "all engine operation"

// TIUNo of selected Engine or use selected TIU for all engine operation
int MyTIUNo;
if (EngineNo == 100) {
    MyTIUNo = TIUSpin->ValueAsInt; // MyTIUNo is not used
    } else {
    MyTIUNo = EngineData->Cells[TIU_NUMBER] [EngineData->Row].ToIntDef(0);
    if (MyTIUNo == 0) {
        DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineData->Row));
        return;
        }
    }
OpenSoundControl(EngineNo, MyTIUNo);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::OpenSoundControl(int Consist, int TIU)
{
assert(Consist >= (Setup->AllowZero->Checked? 0 : 1) && Consist <= 100);	// 0 factory reset, 1-99, 100 "all engine operation"
if (!IsConsistRunning(Consist)) return;	// not running
LOCKEM()
// Manually create form and show it
assert(Consist >= (Setup->AllowZero->Checked? 0 : 1) && Consist <= ALLENGINE);	// 0 factory reset, 1-99, 100 "all engine operation"

const TCursor OldCursor = Screen->Cursor; 	// save cursor
Screen->Cursor = crHourGlass;

// Many Volume Control Forms
if (ptrVolume[Consist] == NULL) {
	ptrVolume[Consist] = new TVolume(this, RemoteNo, Consist, TIU, EngineData->Cells[ENG_NAME][Consist], NULL);     // Pass "this" as the owner
	assert(ptrVolume[Consist] != NULL);	// make sure that construction worked
    ptrVolume[Consist]->ParentHandle = Handle; // for return messages
    // setup defaults on Operations form after startup  (String)
    if ((Consist != 0) && (EngineData->Cells[ENG_NAME][Consist] != "*")) {  // don't load uninitialized data
        if (ptrOp[Consist]->EngineTypeComboBox->ItemIndex == ELECTRIC_ENGINE) {
            ptrVolume[Consist]->BoilerStartupButton->Visible = true;
            ptrVolume[Consist]->BoilerReleaseButton->Visible = true;
        	}
    	try {
            SetSlider(ptrVolume[Consist]->MasterVolume, EngineData->Cells[VOL_MASTER][Consist].ToIntDef(100));
            SetSlider(ptrVolume[Consist]->EngineVolume, EngineData->Cells[VOL_ENGINE][Consist].ToIntDef(100));
            SetSlider(ptrVolume[Consist]->AccentVolume, EngineData->Cells[VOL_ACCENT][Consist].ToIntDef(100));
            SetSlider(ptrVolume[Consist]->HornWhistleVolume, EngineData->Cells[VOL_HORN][Consist].ToIntDef(100));
            // Horn or Whistle label
            if (EngineData->Cells[ENG_TYPE][Consist].ToIntDef(STEAM_ENGINE) == STEAM_ENGINE) {
            	ptrVolume[Consist]->HornWhistleVolume->SignalSettings->Name1 = "Whistle"; 	// Steam Engine
                } else {
            	ptrVolume[Consist]->HornWhistleVolume->SignalSettings->Name1 = "Horn";	// Diesel or Electric Engine
				}
            SetSlider(ptrVolume[Consist]->BellVolume, EngineData->Cells[VOL_BELL][Consist].ToIntDef(100));
  			// Mute is never saved and on Shutdown, the volume is returned to normal
            ptrVolume[Consist]->Repaint();
        	}
        catch ( EConvertError & oops  ){
            // first time this Engine is used, the EngineData StringGrid is empty and the ToIntDef() function
            // returns 100
            DebugMessagePopup("Warning", "Empty Engine Sound Field - Repaired");
	        CaughtErrors->Value ++;
    	    }
        catch (...) {
            DebugMessagePopup("Error", "Unknown Startup Problem");
	        CaughtErrors->Value ++;
        	}
        }
	} else {   // it's already created
	ptrVolume[Consist]->Show();
	}
Screen->Cursor = OldCursor;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::EngineSpinValueChanged(TObject *Sender)
{
// this routine is called whenever something or someone changes EngineSpin->Value
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
assert((EngineSpin->ValueAsInt >= 0) && (EngineSpin->ValueAsInt <= ALLENGINE));	// 0 factory reset, 1-99, 100 "all engine operation"

if (DEBUGGINGX(6)) DB->Debug_Print("Engine Number changed to " + String(EngineNo));
//
if (EngineNo == 0) {	// Engine 0 (DCS #1) is a Factory Reset Engine
    NameofEngine->Text = "Factory Reset Engine";
    FileNamePrefix->Text = NameofEngine->Text;
    } else {
	NameofEngine->Text = EngineData->Cells[ENG_NAME][EngineNo];
	FileNamePrefix->Text = NameofEngine->Text;
	// Highlight the engine in the StringGrid
	EngineData->Col = ENG_NAME;
	EngineData->Row = EngineNo;
    EngineUpDown->Position = EngineData->RowCount - EngineNo;
    EngineData->ScrollBars = ssVertical;    // limit to vertical scroll bars only
    }

// Enable buttons if an operations form is already created
if (ptrOp[EngineNo] != NULL) {
	SoundCtrlsButton->Enabled = true;
	OpButton->Enabled = true;
	} else {
	SoundCtrlsButton->Enabled = false;
	OpButton->Enabled = false;
	}
return;
}
//---------------------------------------------------------------------------

String __fastcall TMainForm::sEncodedStringToHexString(String Cmd)
{
// decode the encoded string into Hex
const int SLen = Cmd.Length();
String DecodedString = "";
//  Decode
    for (int idx = 1 ; idx < SLen; idx += 4) {
       	if (SLen - idx < 3) continue; // don't decode unless we have 4 characters
		String sVal = bintodec8(bitswapRX(Convert16B8BNumber(Cmd.SubString(idx, 4))));
        int iVal = sVal.ToInt();	
        String hVal = IntToHex(iVal & 0xFF, 2);
    	DecodedString += hVal;
        }
return DecodedString;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ReadEnginesButtonClick(TObject *Sender)
{
//
// This reads all engines on all active TIU(s)
//
// Set all engines inactive and then scan each TIU/WTIU in turn, seting engine active.
// If the engine is not in the Engine List, it is added and then set active.
// Engines are never removed from the Engine List.
//
// https://stackoverflow.com/questions/31649315/c-builder-button-click-function-reentry
LOCKEM()

if (Setup->Radio_COM_Mode && Setup->TXOnlyComm->Checked) {	// cannot do this function without full duplex communications
	DebugMessagePopup("Warning", MainForm->TXMsg, "");
    return;
    }

// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
ReadEnginesButton->Enabled = false;

const TCursor OldCursor = Screen->Cursor; 	// save cursor
Screen->Cursor = crHourGlass;

TMessagePopup *MessagePopup = NULL;  // ReadEnginesButtonClick

if (DEBUGGING) STOPem->Enabled = true;
bStop = false;
// please wait message
Application->ProcessMessages(); // process all messages before the popup
if (!MessagePopup) {
	MessagePopup = new TMessagePopup(this, Handle, NULL, NULL);     // Pass "this" as the owner
	assert(MessagePopup != NULL);		// make sure that construction worked
    }
MessagePopup->Show();
MessagePopup->Caption = "Information";
MessagePopup->MessageLine->Caption = "  Reading Engines  ";
if (DEBUGGING) DB->Debug_Print(MessagePopup->Caption + " : " + MessagePopup->MessageLine->Caption + " - " + MessagePopup->StatusLine->Caption);
MessagePopup->Repaint();

// Try to load data from all engines on Layout and powered on
int Found = 0;
int iTIUCount;
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner
//-------------------------------------------------
for (int iEng = 1; iEng < ENGINEMAX; iEng++) {
    // set all engines to inactive
    SetConsistState(iEng, INACTIVE);
    // if we are keeping Engines even if they are not in the TIU, then don't
    // clear the engine name. If we are not keeping engines, then reset the
    // engine name and TIU Number
    if (!Setup->KeepEngines->Checked) {
        EngineData->Cells [ENG_NAME] [iEng] = "*";
        EngineData->Cells [TIU_NUMBER] [iEng] = 0;   // set no TIU associated
        // if the Engine is selected, we must clear out a couple of other fields
        if (EngineNo == iEng) {
            NameofEngine->Text = "*";
            FileNamePrefix->Text = NameofEngine->Text;
            }
        }
    }
byte REbyteResp[CMDMAX]; 	// Array for decoded binary response
// loop over TIUMAX number of TIU
for (int TIU = 1; TIU <= TIUMAX; TIU++) {
    // Send I0 command to get 100 bit string
    if (Setup->WTIU_WiFi_Mode->Checked && WTIUTXRX[TIU - 1] == NULL) continue;    // no WTIU attached to this TIU number
    if (Setup->Radio_COM_Mode->Checked && TIUSelection[TIU - 1] == false) continue;  // no TIU attached to this TIU number
    // I0 command to get engine list, this command takes a long time to get a response
    // Engine Number can be anything - in the Remote it is FACTORYRESET (0)
    bool bretv = ptrTIUCommThread->Send_String_FIFO(EngineNo, "I0", TIU, RemoteNo, 39, I_WAIT, NO_RECORD, NULL, NULL, &iTIUCount, REbyteResp);
    if (!bretv) {   // retry once
        if (DEBUGGING) DB->Debug_Print("ReadEnginesButtonClick() Retrying I0");
        bretv = ptrTIUCommThread->Send_String_FIFO(EngineNo, "I0", TIU, RemoteNo, 39, I_WAIT, NO_RECORD, NULL, NULL, &iTIUCount, REbyteResp);
        }
    if (bretv && (iTIUCount == 39 || iTIUCount == 50)) {    // TIU responds with 39 bytes, WTIU responds with 50 bytes
        // Got the data - convert I0 response to 100 bit string
        // Read Engines uses OneHundredBitString
        if (Setup->Radio_COM_Mode->Checked) {
            // Handle Radio COM
            // the I0 command returns 39 characters
            // pull out the 100 Bit long Engine Encoding starting at character 11 for 26 characters
            //  5AA6A9555A5A5555595A5955555555555555555555555555555555555555555556655555 (0x00)
            //    5B  A8  0F  00  0D  08  00  00  00  00  00  00  00  00  00  00  12  00
            //crc ^^  ^^
            //TIU/Remote  ^^
            //response code   ^^
            //length              ^^  (here its 13 bytes)
            //one bit per engine      ^^                   ....                      ^^
            // the string is in Hex so we really need two characters to get one 8 bit hex value
            // Got a good response
            // Convert REbyteResp to a binary string, eg:
            // "0000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000100100000000000000000"
            String EncodedBinary = byteBinaryStringToBinaryString(&REbyteResp[5], 13);
            if (DEBUGGING) DB->Debug_Print("Read Engines - EncodedBinary : " + EncodedBinary);
            // 6 means skip the first 5 bits
            // then pick up the next 99 bits (it's 98 Engines
            // due to the bug in the TIU code when an AIU is attached.)
            OneHundredBitString = "0" + EncodedBinary.SubString(6, 99);  // 100 bits
            }
        if (Setup->WTIU_WiFi_Mode->Checked) {
            // Handle WTIU ASCII Response   IO:08,00,00,00,00,00,00,00,00,00,00,12,00 okay    (50 chars)
                // this LSB bit set to 1 means there are one or more Factory Reset Engines
                //                              I0:08,00,00,00,00,00,00,00,00,00,00,00,01 okay
            // pull out the 100 Bit long Engine Encoding
            String EncodedBinary = byteCharacterStringToBinaryString(&REbyteResp[3], 13);
            if (DEBUGGING) DB->Debug_Print("Read Engines - EncodedBinary : " + EncodedBinary);
            // 6 means skip the first 5 bits
            // then pick up the next 99 bits (it's 98 Engines
            // due to the bug in the TIU code when an AIU is attached.(see NOTE below))
            OneHundredBitString = "0" + EncodedBinary.SubString(6, 99);  // 100 bits
            }
        if (DEBUGGING) DB->Debug_Print("Read Engines - OneHundredBitString :  " + OneHundredBitString);
        // NOTE: it appears that connecting an AIU causes engine bit 100 to be on and that confuses
        // this routine. See note just above and in for loop below. Routine has been modified
        // to only look up to DCS engine 99
        }  else {
        // command failed or didn't get enough data
        //
        #ifdef OLDWAY
        not used
        DebugMessagePopup("Warning", "         Failed to get list of Engines from TIU " + String(TIU) + "      ", "");
        #else
        MessagePopup->Caption = "Warning";
        MessagePopup->StatusLine->Caption = "         Failed to get list of Engines from TIU " + String(TIU) + "      ";
        if (DEBUGGING) DB->Debug_Print(MessagePopup->Caption + " : " + MessagePopup->MessageLine->Caption + " - " + MessagePopup->StatusLine->Caption);
        MessagePopup->Repaint();
        Delay(UIPOPUPTIMEOUT);
        #endif
        continue;   // on to next TIU
        }
    // Count the number of 1 bits on in the Binary string
    int EngCount = OneBitsinBinaryString(OneHundredBitString);
    // loop through all engines of I0 response
    // start with next to LSB of string, which is DCS engine #2 (LSB of string is DCS engine #1 or a Factory Reset Engine
    // go through the string from DSC engine #2 to DCS engine #99  (Engines 1-98)
    for (int DCSEngNo = 2 ; DCSEngNo <= ENGINEMAX ; DCSEngNo++) {  // DCS Engines 2 through 99 (max 98 of them). Skip DCS #100
                         // must be one higher than actual Engine Number
        // TODO : Due to error in TIU, if any AIU is connected, then
        // the bit for engine 99 (DCS 100) is on. Need to skip that bit for now
        // that is, check only up to Engine 98 (DCS 99)
        // That was not enough because now I've seen that the cell phone app limits
        // the engine number to 97. I may have to disable both engines 99 and 98
        // The question becomes why was engine 98 also disabled in the remote? Maybe
        // that bit is used for something else also.
        // For each bit that is on, send an Ixxx command to get the engine name
        if (bStop) {
            break;
            }
        // if the appropriate bit is not on, then continue the loop
        // The [] operator of a String or AnsiString assumes a base index of 1
        if (OneHundredBitString[(100 + 1) - DCSEngNo] != '1') {
            continue;  // Engine not in TIU, go on to next engine
            }
        // status message
        MessagePopup->Caption = "Information";
        MessagePopup->StatusLine->Caption = "TIU #" + String(TIU) + " : Engine # " + String(DCSEngNo - 1) +
            " of " + String(EngCount) + " Engine(s) Total";
        if (DEBUGGING) DB->Debug_Print(MessagePopup->Caption + " : " + MessagePopup->MessageLine->Caption + " - " + MessagePopup->StatusLine->Caption);
        MessagePopup->Repaint();
        // no Sleep() call after this repaint
        //
        // Convert Engine Number into chars for the command
        // NOTE: the Engine Number as digits in the command do matter
        char Command[10];
        (void)snprintf(Command, sizeof Command, "I%d", DCSEngNo);	// Uses the DCS Engine Number (Read Engines)
        bool bretv = ptrTIUCommThread->Send_String_FIFO(DCSEngNo - 1, Command, TIU, RemoteNo, 173, I_WAIT, NO_RECORD, NULL, NULL, &iTIUCount, REbyteResp);
        if (!bretv) {	// retry once
            if (DEBUGGING) DB->Debug_Print("ReadEnginesButtonClick() Retrying " + String(Command));
            bretv = ptrTIUCommThread->Send_String_FIFO(DCSEngNo - 1, Command, TIU, RemoteNo, 173, I_WAIT, NO_RECORD, NULL, NULL, &iTIUCount, REbyteResp);
            }
        unsigned char iEngType = 0x00;
        //int iLen = strlen(Command);
        if (bretv && iTIUCount >= 173) {    // Radio is 173 bytes, WiFi is around 220 bytes depending on the # characters in engine name
            if (Setup->Radio_COM_Mode->Checked) {
                // Handle Radio COM
                // Got a good response
                // Get the info about whether this is a diesel or steam engine, use
                // that info to enable or disable certain switches
                //
                // Response to Ixxx command:
                // 996AA5A65A5A555556A5555A55A566595999559A655556A656AA6566656A6565656566566566656A656A6669AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA555555555555555555555555555555555555555556556595AA6A55955555555555555555555556559A99696569A555555A5AA9A955555555555555555556 (0x00)
                //   9D  F1  0F  00  52  05  50  26  4C  45  20  53  57  31  35  30  30  23  31  35  35  36  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  00  00  00  00  00  00  00  00  00  00  02  60  BF  40  00  00  00  00  00  02  CE  38  78  00  0F  FC  00  00  00  00  01
                //                     ^^^^ is is 0x00/0x10 for Steam, 0x05/0x85 for Diesel, and 0x25 Gas/Electric
                //
                iEngType = REbyteResp[5] & 0x0F;
                EngineData->Cells [ENG_NAME] [DCSEngNo - 1] = byteExtractEngineName(REbyteResp);  // put Engine Name into the EngineData TStringGrid
                EngineData->Cells [TIU_NUMBER] [DCSEngNo - 1] = TIU;      // put Engine's TIU number into the EngineData TStringGrid
                }
            if (Setup->WTIU_WiFi_Mode->Checked) {    // in WTIU mode, we don't get an exact number of bytes so have to compare the command and engine number
                // Handle WTIU ASCII Response I10:05;P&LE SW1500#1556;FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,00,00,00,00,00,00,00,00,00,00,02,60,BF,40,00,00,00,00,00,02,CE,38,78,00,0F,FC,00,00,00,00,00,63;01 okay
                // Got a good response
                // Get the info about whether this is a diesel or steam engine, use
                // that info to enable or disable certain switches
                unsigned int i1;
                char cName[17];
                int retv = sscanf(REbyteResp, "I%*d:%x;%[^;]", &i1, cName);   // %x requires &int
                assert(retv == 2);
                iEngType = i1;
                EngineData->Cells [ENG_NAME] [DCSEngNo - 1] = cName;    // put Engine Name into the EngineData TStringGrid
                EngineData->Cells [TIU_NUMBER] [DCSEngNo - 1] = TIU;      // put Engine's TIU number into the EngineData TStringGrid
                }

            // Eng Type = 0x00 for Steam
            // Eng Type = 0x05 for Diesel
            // Eng Type = 0x85 for newer PS3 Diesels
            // Eng Type = 0x25 for electric (PCC Car)
            // Eng Type = 0x10 for Big Boy #4014
            // there may be others but I haven't seen them
            if (DEBUGGING) DB->Debug_Print("Found Engine " + String(DCSEngNo - 1) + " : 0x" + IntToHex(iEngType, 2));
            switch(iEngType) {
                case 0x00:
                case 0x10:
                    EngineData->Cells [ENG_TYPE] [DCSEngNo - 1] = STEAM_ENGINE;
                    break;
                case 0x05:
                case 0x85:
                    EngineData->Cells [ENG_TYPE] [DCSEngNo - 1] = DIESEL_ENGINE;
                    break;
                case 0x25:
                    EngineData->Cells [ENG_TYPE] [DCSEngNo - 1] = ELECTRIC_ENGINE;
                    break;
                default:
                    if (DEBUGGING) DB->Debug_Print("Found an unknown engine type of 0x" + IntToHex(iEngType, 2) + ". Setting it to Diesel");
                    EngineData->Cells [ENG_TYPE] [DCSEngNo - 1] = DIESEL_ENGINE;
                    break;
                }
            //
            // This is a guess at determining if this is a PS2 or PS3 engine
            //
            byte ival;
			bool bretv = FetchByte(0x50, &ival, DCSEngNo - 1, TIU);
            if (!bretv)
                ival = 0;           // default
            if (ival == 0x00) {
				EngineData->Cells[ENG_PS][DCSEngNo - 1] = PS3;
				PSType[DCSEngNo - 1] = PS3; // information for the PC threads
				} else {
				EngineData->Cells[ENG_PS][DCSEngNo - 1] = PS2;
				PSType[DCSEngNo - 1] = PS2; // information for the PC threads
				}
            // clear the cache
            ScalingFactor[DCSEngNo - 1] = 0.0;

            SetConsistState(DCSEngNo - 1, ACTIVE);		// set active
            if (EngineNo == (DCSEngNo - 1)) {
                // if the Engine is selected, we must set a couple of other fields
                NameofEngine->Text = EngineData->Cells [ENG_NAME] [DCSEngNo - 1];
                FileNamePrefix->Text = NameofEngine->Text;
                }
            Found++;
            } else {
            // command failed or didn't get enough data so clear the ENG_NAME cell in the Engine List
            EngineData->Cells [ENG_NAME] [DCSEngNo - 1] = "*";
            // if the Engine is selected, we must clear out a couple of other fields
            if (EngineNo == (DCSEngNo - 1)) {
                NameofEngine->Text = "*";
                FileNamePrefix->Text = NameofEngine->Text;
                }
            #ifdef OLDWAY
            not used
            DebugMessagePopup("Warning", "Read Engine Failed - Engine " + String(DCSEngNo - 1), "");
            #else
            MessagePopup->Caption = "Error";
            MessagePopup->StatusLine->Caption = "Read Engine Failed - Engine " + String(DCSEngNo - 1);
            if (DEBUGGING) DB->Debug_Print(MessagePopup->Caption + " : " + MessagePopup->MessageLine->Caption + " - " + MessagePopup->StatusLine->Caption);
            MessagePopup->Repaint();
            Delay(UIPOPUPTIMEOUT);
            #endif
            continue;  // no engine name response, go on to next engine
            }
        }
    // Look at LSB of string which is DCS engine #1 or a Factory Reset engine
    // if the bit is off, there are no more engines to add
    // if the bit is on, notify the op but don't add it to the Engine List.
    // The [] operator of a String or AnsiString assumes a base index of 1
    if (OneHundredBitString[(100 + 1) - 1] == '1') {
        Delay(UIPOPUPTIMEOUT);    // short delay so user can read the previous message
        // Factory Reset engine is found - remind user of what to do
        MessagePopup->Caption = "Information";
        MessagePopup->StatusLine->Caption = "Factory Reset Engine found on TIU #" + String(TIU) + " - use [Read]->Add Factory Reset Engine";
        if (DEBUGGING) DB->Debug_Print(MessagePopup->Caption + " : " + MessagePopup->MessageLine->Caption + " - " + MessagePopup->StatusLine->Caption);
        MessagePopup->Repaint();
        }
	Delay(UIPOPUPTIMEOUT);    // a short pause between TIU's
    }
// redraw the control
EngineData->Repaint();
// Update Active/Inactive Engine Counts
CountActiveEngines();
// update users of the Engine List
UpdateEngDataUsers();
//
#ifdef OLDWAY
not used
if (Found == 0) {
	DebugMessage("Read Engines Complete - No Engines Found");
	} else {
    DebugMessage("Read Engines Complete - Found " + String(Found) + " Engine(s)");
    }
#else
MessagePopup->Caption = "Information";
if (Found == 0) {
	MessagePopup->StatusLine->Caption = "Read Engines Complete - No Engines Found";
	} else {
    MessagePopup->StatusLine->Caption = "Read Engines Complete - Found " + String(Found) + " Engine(s)";
    }
if (DEBUGGING) DB->Debug_Print(MessagePopup->Caption + " : " + MessagePopup->MessageLine->Caption + " - " + MessagePopup->StatusLine->Caption);
MessagePopup->Repaint();
Delay(UIPOPUPTIMEOUT);
#endif
//
STOPem->Enabled = false;
if (MessagePopup != NULL) {
    MessagePopup->Hide();
    delete MessagePopup;
    MessagePopup = NULL;
    }
Screen->Cursor = OldCursor;		// restore Cursor
//
ReadEnginesButton->Enabled = true;
return;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::FindEnginesButtonClick(TObject *Sender)
{
// Debug button - Find Engines on EngineList on all TIU/WTIU
FindEngines();  // on all TIU
}
//---------------------------------------------------------------------------

bool __fastcall TMainForm::FindEngines(void)
{
//
// This finds all engines on the Layout on all active TIU(s)
//
// Set all engines in Engine List to inactive and then scan each TIU/WTIU in turn, seting the engine active if
// the engine already appears in the Engine List.
// (If the engine does not appear in the Engine List, the user will have
// to press the [Read] button to add the engine to the Engine List.)
//
// Performs the same function as FindEngines(void) but for all TIU
//
// Performs the same function as ReadEngineButtonClick()
// but only debug and error messages are displayed (no status messages)
//
// Returns true on success, false on error
//
if (Setup->Radio_COM_Mode && Setup->TXOnlyComm->Checked) {	// cannot do this function without full duplex communications
	DebugMessagePopup("Warning", MainForm->TXMsg, "");
    return false;
    }

// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return false;
    }

if (DEBUGGING) DB->Debug_Print("FindEngines() : Reading Engines on all TIU/WTIU");

// Try to load data from all engines on Layout and powered on
int Found = 0;
int iTIUCount;
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner
//-------------------------------------------------
for (int iEng = 1; iEng < ENGINEMAX; iEng++) {
    // set all engines to inactive
    SetConsistState(iEng, INACTIVE);
#ifdef NOTNEEDED
    // if we are keeping Engines even if they are not in the TIU, then don't
    // clear the engine name. If we are not keeping engines, then reset the
    // engine name and TIU Number
    if (!Setup->KeepEngines->Checked) {
        EngineData->Cells [ENG_NAME] [iEng] = "*";
        EngineData->Cells [TIU_NUMBER] [iEng] = 0;   // set no TIU associated
        // if the Engine is selected, we must clear out a couple of other fields
        if (EngineNo == iEng) {
            NameofEngine->Text = "*";
            FileNamePrefix->Text = NameofEngine->Text;
            }
        }
#endif
    }
byte REbyteResp[CMDMAX]; 	// Array for decoded binary response
// loop over TIUMAX number of TIU
for (int TIU = 1; TIU <= TIUMAX; TIU++) {
    // Send I0 command to get 100 bit string
    if (Setup->WTIU_WiFi_Mode->Checked && WTIUTXRX[TIU - 1] == NULL) continue;    // no WTIU attached to this TIU number
    if (Setup->Radio_COM_Mode->Checked && TIUSelection[TIU - 1] == false) continue;  // no TIU attached to this TIU number
    // I0 command to get engine list, this command takes a long time to get a response
    // Engine Number can be anything - in the Remote it is FACTORYRESET (0)
    bool bretv = ptrTIUCommThread->Send_String_FIFO(EngineNo, "I0", TIU, RemoteNo, 39, I_WAIT, NO_RECORD, NULL, NULL, &iTIUCount, REbyteResp);
    if (!bretv) {   // retry once
        if (DEBUGGING) DB->Debug_Print("FindEngines() : Retrying I0");
        bretv = ptrTIUCommThread->Send_String_FIFO(EngineNo, "I0", TIU, RemoteNo, 39, I_WAIT, NO_RECORD, NULL, NULL, &iTIUCount, REbyteResp);
        }
    if (bretv && (iTIUCount == 39 || iTIUCount == 50)) {    // TIU responds with 39 bytes, WTIU responds with 50 bytes
        // Got the data - convert I0 response to 100 bit string
        // Read Engines uses OneHundredBitString
        if (Setup->Radio_COM_Mode->Checked) {
            // Handle Radio COM
            // the I0 command returns 39 characters
            // pull out the 100 Bit long Engine Encoding starting at character 11 for 26 characters
            //  5AA6A9555A5A5555595A5955555555555555555555555555555555555555555556655555 (0x00)
            //    5B  A8  0F  00  0D  08  00  00  00  00  00  00  00  00  00  00  12  00
            //crc ^^  ^^
            //TIU/Remote  ^^
            //response code   ^^
            //length              ^^  (here its 13 bytes)
            //one bit per engine      ^^                   ....                      ^^
            // the string is in Hex so we really need two characters to get one 8 bit hex value
            // Got a good response
            // Convert REbyteResp to a binary string, eg:
            // "0000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000100100000000000000000"
            String EncodedBinary = byteBinaryStringToBinaryString(&REbyteResp[5], 13);
            if (DEBUGGING) DB->Debug_Print("FindEngines() : EncodedBinary : " + EncodedBinary);
            // 6 means skip the first 5 bits
            // then pick up the next 99 bits (it's 98 Engines
            // due to the bug in the TIU code when an AIU is attached.)
            OneHundredBitString = "0" + EncodedBinary.SubString(6, 99);  // 100 bits
            }
        if (Setup->WTIU_WiFi_Mode->Checked) {
            // Handle WTIU ASCII Response   IO:08,00,00,00,00,00,00,00,00,00,00,12,00 okay    (50 chars)
                // this LSB bit set to 1 means there are one or more Factory Reset Engines
                //                              I0:08,00,00,00,00,00,00,00,00,00,00,00,01 okay
            // pull out the 100 Bit long Engine Encoding
            String EncodedBinary = byteCharacterStringToBinaryString(&REbyteResp[3], 13);
            if (DEBUGGING) DB->Debug_Print("FindEngines() : EncodedBinary : " + EncodedBinary);
            // 6 means skip the first 5 bits
            // then pick up the next 99 bits (it's 98 Engines
            // due to the bug in the TIU code when an AIU is attached.(see NOTE below))
            OneHundredBitString = "0" + EncodedBinary.SubString(6, 99);  // 100 bits
            }
        if (DEBUGGING) DB->Debug_Print("FindEngines() : OneHundredBitString :  " + OneHundredBitString);
        // NOTE: it appears that connecting an AIU causes engine bit 100 to be on and that confuses
        // this routine. See note just above and in for loop below. Routine has been modified
        // to only look up to DCS engine 99
        }  else {
        // command failed or didn't get enough data
        //
        if (DEBUGGING) DB->Debug_Print("FindEngines() : Failed to get list of Engines from TIU " + String(TIU));
        continue;   // on to next TIU
        }
    // loop through all engines of I0 response
    // start with next to LSB of string, which is DCS engine #2 (LSB of string is DCS engine #1 or a Factory Reset Engine
    // go through the string from DSC engine #2 to DCS engine #99  (Engines 1-98)
    for (int DCSEngNo = 2 ; DCSEngNo <= ENGINEMAX ; DCSEngNo++) {  // DCS Engines 2 through 99 (max 98 of them). Skip DCS #100
                         // must be one higher than actual Engine Number
        // TODO : Due to error in TIU, if any AIU is connected, then
        // the bit for engine 99 (DCS 100) is on. Need to skip that bit for now
        // that is, check only up to Engine 98 (DCS 99)
        // That was not enough because now I've seen that the cell phone app limits
        // the engine number to 97. I may have to disable both engines 99 and 98
        // The question becomes why was engine 98 also disabled in the remote? Maybe
        // that bit is used for something else also.
        // For each bit that is on, send an Ixxx command to get the engine name
        if (bStop) {
            break;
            }
        // if the appropriate bit is not on, then continue the loop
        // The [] operator of a String or AnsiString assumes a base index of 1
        if (OneHundredBitString[(100 + 1) - DCSEngNo] != '1') {
            continue;  // Engine not in TIU, go on to next engine
            }
        //
        // if the engine name is present, the engine has been found in the past by the [Read] button
        // so just set it active and insert its TIU number
        // If the engine name is "*", then we require a push of the [Read] button 
        if (EngineData->Cells [ENG_NAME] [DCSEngNo - 1] != "*") {
            EngineData->Cells [TIU_NUMBER] [DCSEngNo - 1] = TIU;      // put Engine's TIU number into the EngineData TStringGrid
            SetConsistState(DCSEngNo - 1, ACTIVE);		// set active
            Found++;
            // status message
            if (DEBUGGING) DB->Debug_Print( "FindEngines() : TIU #" + String(TIU) + " : Engine # " + String(DCSEngNo - 1) +
                " set active");
            } else {
            // status message
            if (DEBUGGING) DB->Debug_Print( "FindEngines() : TIU #" + String(TIU) + " : Engine # " + String(DCSEngNo - 1) +
                " on layout but not in Engine List");
            }
        }
    // Look at LSB of string which is DCS engine #1 or a Factory Reset engine
    // if the bit is off, there are no more engines to add
    // if the bit is on, notify the op but don't add it to the Engine List.
    // The [] operator of a String or AnsiString assumes a base index of 1
    if (OneHundredBitString[(100 + 1) - 1] == '1') {
        // Factory Reset engine is found - remind user of what to do
        if (DEBUGGING) DB->Debug_Print("FindEngines() : Factory Reset Engine found on TIU #" + String(TIU) + " - use [Read]->Add Factory Reset Engine");
        }
    }
// redraw the control
EngineData->Repaint();
// Update Active/Inactive Engine Counts
CountActiveEngines();
//
// update users of the Engine List
UpdateEngDataUsers();

if (Found == 0) {
	if (DEBUGGING) DB->Debug_Print("FindEngines() : Complete - No Active Engines Found");
	} else {
    if (DEBUGGING) DB->Debug_Print("FindEngines() : Complete - Found " + String(Found) + " Active Engine(s)");
    }
//
return true;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CMSendWatchdog(TMessage &Message)
{
int MyTIUNo = Message.WParam;
assert(MyTIUNo >= 1 && MyTIUNo <= TIUMAX);
int MyEngineNo = Message.LParam;
assert(MyEngineNo >= FACTORYRESET && MyEngineNo <= ENGINEMAX);
//
// Start a Watchdog thread to send a Watchdog Signal and count any new active engines found
//
	// Create suspended - in BCB 4.0, the thread constructor does not wait for lower level constructors itself.
    // The Execute() routine actually runs before some lower level construction is finished.
    // Don't put constructions in the thread constructor. if you have to, start the thread suspended and then start it running
    // per Remy LeBeau (TeamB) it is a known bug that was fixed in BCB 6.0
Watchdog_Thread * ptrWatchdog_Thread = new Watchdog_Thread(true, RemoteNo, MyTIUNo, MyEngineNo, false);
assert(ptrWatchdog_Thread);
#if __BORLANDC__ >= 0x720
ptrWatchdog_Thread->Start();			// start the thread for CB10.1
#else
ptrWatchdog_Thread->Resume();			// start the thread for BCB4.0 - use deprecated in CB10.1, use Start() instead
#endif
}
//---------------------------------------------------------------------------

bool __fastcall TMainForm::FindEngines(int TIU)
{
//
// This finds all engines on the Layout on the given TIU
//
// Scans the given TIU/WTIU, seting the engine active if
// the engine already appears in the Engine List.
// (If the engine does not appear in the Engine List, the user will have
// to press the [Read] button to add the engine to the Engine List.)
//
// Performs the same function as FindEngines(void) but for 1 TIU
// but nly debug and error messages are displayed (no status messages)
//
// Returns true on success, false on error
//
if (Setup->Radio_COM_Mode && Setup->TXOnlyComm->Checked) {	// cannot do this function without full duplex communications
	DebugMessagePopup("Warning", MainForm->TXMsg, "");
    return false;
    }

// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return false;
    }

if (DEBUGGING) DB->Debug_Print("FindEngines(TIU) : Reading Engines on TIU/WTIU #" + String(TIU));

// Try to load data from all engines on a particular TIU, on the Layout and powered on
int Found = 0;
int iTIUCount;
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner
//-------------------------------------------------
byte REbyteResp[CMDMAX]; 	// Array for decoded binary response

// perform operation for 1 TIU
do {
    // Send I0 command to get 100 bit string
    if (Setup->WTIU_WiFi_Mode->Checked && WTIUTXRX[TIU - 1] == NULL) return false;    // no WTIU attached to this TIU number
    if (Setup->Radio_COM_Mode->Checked && TIUSelection[TIU - 1] == false) return false;  // no TIU attached to this TIU number
    // I0 command to get engine list, this command takes a long time to get a response
    // Engine Number can be anything - in the Remote it is FACTORYRESET (0)
    bool bretv = ptrTIUCommThread->Send_String_FIFO(EngineNo, "I0", TIU, RemoteNo, 39, I_WAIT, NO_RECORD, NULL, NULL, &iTIUCount, REbyteResp);
    if (!bretv) {   // retry once
        if (DEBUGGING) DB->Debug_Print("FindEngines(TIU) : Retrying I0");
        bretv = ptrTIUCommThread->Send_String_FIFO(EngineNo, "I0", TIU, RemoteNo, 39, I_WAIT, NO_RECORD, NULL, NULL, &iTIUCount, REbyteResp);
        }
    if (bretv && (iTIUCount == 39 || iTIUCount == 50)) {    // TIU responds with 39 bytes, WTIU responds with 50 bytes
        // Got the data - convert I0 response to 100 bit string
        // Read Engines uses OneHundredBitString
        if (Setup->Radio_COM_Mode->Checked) {
            // Handle Radio COM
            // the I0 command returns 39 characters
            // pull out the 100 Bit long Engine Encoding starting at character 11 for 26 characters
            //  5AA6A9555A5A5555595A5955555555555555555555555555555555555555555556655555 (0x00)
            //    5B  A8  0F  00  0D  08  00  00  00  00  00  00  00  00  00  00  12  00
            //crc ^^  ^^
            //TIU/Remote  ^^
            //response code   ^^
            //length              ^^  (here its 13 bytes)
            //one bit per engine      ^^                   ....                      ^^
            // the string is in Hex so we really need two characters to get one 8 bit hex value
            // Got a good response
            // Convert REbyteResp to a binary string, eg:
            // "0000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000100100000000000000000"
            String EncodedBinary = byteBinaryStringToBinaryString(&REbyteResp[5], 13);
            if (DEBUGGING) DB->Debug_Print("FindEngines(TIU) : EncodedBinary : " + EncodedBinary);
            // 6 means skip the first 5 bits
            // then pick up the next 99 bits
            // TODO : (it's 98 Engines due to the bug in the TIU code when an AIU is attached.)
            OneHundredBitString = "0" + EncodedBinary.SubString(6, 99);  // 100 bits
            }
        if (Setup->WTIU_WiFi_Mode->Checked) {
            // Handle WTIU ASCII Response   IO:08,00,00,00,00,00,00,00,00,00,00,12,00 okay    (50 chars)
                // this LSB bit set to 1 means there are one or more Factory Reset Engines
                //                              I0:08,00,00,00,00,00,00,00,00,00,00,00,01 okay
            // pull out the 100 Bit long Engine Encoding
            String EncodedBinary = byteCharacterStringToBinaryString(&REbyteResp[3], 13);
            if (DEBUGGING) DB->Debug_Print("FindEngines(TIU) : EncodedBinary : " + EncodedBinary);
            // 6 means skip the first 5 bits
            // then pick up the next 99 bits
            // TODO : (it's 98 Engines due to the bug in the TIU code when an AIU is attached.(see NOTE below))
            OneHundredBitString = "0" + EncodedBinary.SubString(6, 99);  // 100 bits
            }
        if (DEBUGGING) DB->Debug_Print("FindEngines(TIU) : OneHundredBitString :  " + OneHundredBitString);
        // NOTE: it appears that connecting an AIU causes engine bit 100 to be on and that confuses
        // this routine. See note just above and in for loop below. Routine has been modified
        // to only look up to DCS engine 99
        }  else {
        // command failed or didn't get enough data
        //
        if (DEBUGGING) DB->Debug_Print("FindEngines(TIU) : Failed to get list of Engines from TIU " + String(TIU));
        return false;   // failure
        }
    // loop through all engines of I0 response
    // start with next to LSB of string, which is DCS engine #2 (LSB of string is DCS engine #1 or a Factory Reset Engine
    // go through the string from DSC engine #2 to DCS engine #99  (Engines 1-98)
    for (int DCSEngNo = 2 ; DCSEngNo <= ENGINEMAX ; DCSEngNo++) {  // DCS Engines 2 through 99 (max 98 of them). Skip DCS #100
                         // must be one higher than actual Engine Number
        // TODO : Due to error in TIU, if any AIU is connected, then
        // the bit for engine 99 (DCS 100) is on. Need to skip that bit for now
        // that is, check only up to Engine 98 (DCS 99)
        // That was not enough because now I've seen that the cell phone app limits
        // the engine number to 97. I may have to disable both engines 99 and 98
        // The question becomes why was engine 98 also disabled in the remote? Maybe
        // that bit is used for something else also.
        // For each bit that is on, send an Ixxx command to get the engine name
        if (bStop) {
            break;
            }
        // if the appropriate bit is not on, then continue the loop
        // The [] operator of a String or AnsiString assumes a base index of 1
        if (OneHundredBitString[(100 + 1) - DCSEngNo] != '1') {
            continue;  // Engine not in TIU, go on to next engine
            }
        //
        // if the engine name is present, the engine has been found in the past by the [Read] button
        // so just set it active and insert its TIU number
        // If the engine name is "*", then we require a push of the [Read] button 
        if (EngineData->Cells [ENG_NAME] [DCSEngNo - 1] != "*") {
            EngineData->Cells [TIU_NUMBER] [DCSEngNo - 1] = TIU;      // put Engine's TIU number into the EngineData TStringGrid
            SetConsistState(DCSEngNo - 1, ACTIVE);		// set active
            Found++;
            // status message
            if (DEBUGGING) DB->Debug_Print( "FindEngines(TIU) : TIU #" + String(TIU) + " : Engine # " + String(DCSEngNo - 1) +
                " set active");
            } else {
            // status message
            if (DEBUGGING) DB->Debug_Print( "FindEngines(TIU) : TIU #" + String(TIU) + " : Engine # " + String(DCSEngNo - 1) +
                " on layout but not in Engine List");
            }
        }
    break;
    } while(true);  // do{} block for ease of breaking out - code is only executed once

// Look at LSB of string which is DCS engine #1 or a Factory Reset engine
// if the bit is off, there are no more engines to add
// if the bit is on, notify the op but don't add it to the Engine List.
// The [] operator of a String or AnsiString assumes a base index of 1
if (OneHundredBitString[(100 + 1) - 1] == '1') {
    // Factory Reset engine is found - remind user of what to do
    if (DEBUGGING) DB->Debug_Print("FindEngines(TIU) : Factory Reset Engine found on TIU #" + String(TIU) + " - use [Read]->Add Factory Reset Engine");
    }

// redraw the control
EngineData->Repaint();
// Update Active/Inactive Engine Counts
CountActiveEngines();
//
// update users of the Engine List
UpdateEngDataUsers();

if (DEBUGGING && Found == 0) {
	if (DEBUGGING) DB->Debug_Print("FindEngines(TIU) : Complete - No Active Engines Found");
	} else {
    if (DEBUGGING) DB->Debug_Print("FindEngines(TIU) : Complete - Found " + String(Found) + " Active Engine(s)");
    }
//
return true;
}
//---------------------------------------------------------------------------
void __fastcall TMainForm::AddFactoryResetEngineClick(TObject *Sender)
{
// Add Engine - Add a Factory Reset engine to the engine list
//
// Look for Engine with DCS number 1 and change the number.
// In the Remote, it will be engine number zero (FACTORYRESET)
// Ask for the new engine number.
//
// https://stackoverflow.com/questions/31649315/c-builder-button-click-function-reentry
LOCKEM()

if (Setup->Radio_COM_Mode && Setup->TXOnlyComm->Checked) {	// cannot do this function without full duplex communications
	DebugMessagePopup("Warning", MainForm->TXMsg, "");
    return;
    }

// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
// disable the AddFactoryResetEngines menu item while processing AddFactoryResetEnginesClick();
AddFactoryResetEngine->Enabled = false;

const TCursor OldCursor = Screen->Cursor; 	// save cursor
Screen->Cursor = crHourGlass;

TMessagePopup *MessagePopup = NULL;  // AddFactoryResetEngineClick

if (DEBUGGING) STOPem->Enabled = true;
bStop = false;
// please wait message
Application->ProcessMessages(); // process all messages before the popup
if (!MessagePopup) {
	MessagePopup = new TMessagePopup(this, Handle, NULL, NULL);     // Pass "this" as the owner
	assert(MessagePopup != NULL);		// make sure that construction worked
    }
MessagePopup->Show();
MessagePopup->Caption = "Information";
MessagePopup->MessageLine->Caption = "  Searching for Factory Reset Engine  ";
if (DEBUGGING) DB->Debug_Print(MessagePopup->Caption + " : " + MessagePopup->MessageLine->Caption + " - " + MessagePopup->StatusLine->Caption);
MessagePopup->Repaint();
// Look for Factory Reset engines and change their engine number to a valid unused number
int Found = 0;
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
// Send "I0" command to get 100 bit string
// "I0" command to get engine list, this command takes a long time to get a response and generates a lot of DCS signal traffic on the track
// Add Factory Reset Engines uses OneHundredBitString
byte REbyteResp[CMDMAX]; 	// Array for decoded binary response
int iTIUCount;
AnsiString EngineName;      // AnsiString because .c_str() is taken
unsigned char iEngType = 0x00;
// Finds one Factory Reset Engine on each click
while(1) {      // use while(1) for ease of breaking out on error
    // loop over TIUMAX number of WIU
    for (int TIU = 1; TIU <= TIUMAX; TIU++) {
        // Send I0 command to get 100 bit string
        if (Setup->WTIU_WiFi_Mode->Checked && WTIUTXRX[TIU - 1] == NULL) continue;    // no WTIU attached to this TIU number
        if (Setup->Radio_COM_Mode->Checked && TIUSelection[TIU - 1] == false) continue;  // no TIU attached to this TIU number
        // I0 command to get engine list, this command takes a long time to get a response
        // Engine Number can be anything - in the Remote it is FACTORYRESET (0)
        bool bretv = ptrTIUCommThread->Send_String_FIFO(EngineNo, "I0", TIU, RemoteNo, 39, I_WAIT, NO_RECORD, NULL, NULL, &iTIUCount, REbyteResp);
        // no retries
        if (bretv && (iTIUCount == 39 || iTIUCount == 50)) {	// the I0 command returns 39 or 50 characters
            if (Setup->Radio_COM_Mode->Checked) {
                // Handle Radio COM
                // the I0 command returns 39 characters
                // pull out the 100 Bit long Engine Encoding starting at character 11 for 26 characters
                // 5AA6A9555A5A5555595A5955555555555555555555555555555555555555555556655555 (0x00)
                //    5B  A8  0F  00  0D  08  00  00  00  00  00  00  00  00  00  00  12  00
                //crc ^^  ^^
                //TIU/Remote  ^^
                //response code   ^^
                //length              ^^  (here its 13 bytes)
                //one bit per engine      ^^                   ....                      ^^
                // the LSB set to 1 means there are one or more Factory Reset Engines
                // the string is in Hex so we really need two characters to get one 8 bit hex value
                // Got a good response
                // Convert REbyteResp to a binary string, eg:
                // "0000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000100100000000000000000"
                String EncodedBinary = byteBinaryStringToBinaryString(&REbyteResp[5], 13);
                if (DEBUGGING) DB->Debug_Print("Add Factory Reset Engine - EncodedBinary : " + EncodedBinary);
                // 6 means skip the first 5 bits
                // then pick up the next 99 bits
                // TODO : (it's 98 Engines due to the bug in the TIU code when an AIU is attached.)
                OneHundredBitString = "0" + EncodedBinary.SubString(6, 99);  // 100 bits
                }
            if (Setup->WTIU_WiFi_Mode->Checked) {
                // Handle WTIU ASCII Response   IO:08,00,00,00,00,00,00,00,00,00,00,12,00 okay    (50 chars)
                // the LSB set to 1 means there are one or more Factory Reset Engines
                //                              I0:08,00,00,00,00,00,00,00,00,00,00,00,01 okay
                // pull out the 100 Bit long Engine Encoding
                String EncodedBinary = byteCharacterStringToBinaryString(&REbyteResp[3], 13);
                // 6 means skip the first 5 bits
                // then pick up the next 99 bits
                // TODO : (it's 98 Engines due to the bug in the TIU code when an AIU is attached.(see NOTE below))
                OneHundredBitString = "0" + EncodedBinary.SubString(6, 99);  // 100 bits
                }
            if (DEBUGGING) DB->Debug_Print("Add Factory Reset Engines - OneHundredBitString :  " + OneHundredBitString);
            } else {
            // command failed or didn't get enough data
            //
            #ifdef OLDWAY
            not used
            DebugMessagePopup("Warning", "         Failed to get list of Engines from TIU " + String(TIU) + "      ", "");
            #else
            MessagePopup->Caption = "Warning";
            MessagePopup->StatusLine->Caption = "         Failed to get list of Engines from TIU " + String(TIU) + "      ";
            if (DEBUGGING) DB->Debug_Print(MessagePopup->Caption + " : " + MessagePopup->MessageLine->Caption + " - " + MessagePopup->StatusLine->Caption);
            MessagePopup->Repaint();
            Delay(UIPOPUPTIMEOUT);
            #endif
            break;  // exit on error
            }
        // Look at LSB of string which is DCS engine #1 or a Factory Reset engine
        // if the bit is off, there are no more engines to add
        // if the bit is on, then add the found Factory Reset engine
        // The [] operator of a String or AnsiString assumes a base index of 1
        if (OneHundredBitString[(100 + 1) - 1] == '1') {
            // Factory Reset engine is found
            // readup its data including engine name
            // if the bit is on, send an I1 command to get the engine name

            // NOTE: EngineNo in the command does not seem to matter
            // NOTE: the Engine Number as digits in the command do matter
            bretv = ptrTIUCommThread->Send_String_FIFO(0, "I1", TIU, RemoteNo, 173, I_WAIT, NO_RECORD, NULL, NULL, &iTIUCount, REbyteResp);

            if (!bretv || (iTIUCount < 173)) {
                DebugMessage(" Add Factory Reset Engine Failed ");
                break;  // no engine name response
                }
            if (Setup->Radio_COM_Mode->Checked) {	// In Radio mode, the I command returns 173 characters if an engine is found with that number
                // Got a good response, get the engine name and then ask for new engine number
                // Get the info about whether this is a diesel or steam engine, use
                // that info to enable or disable certain switches
                //
                // Beginning of response to Ixxx command:
                // 99A55AA95A5A555556A555555596665956A665556565695A......
                //   D8  5E  0F  00  52  00  41  26  53  20  30  2D
                //                     ^^^^ is 0x00/0x10 for Steam, 0x05/0x85 for Diesel, and 0x25 Gas/Electric
                iEngType = REbyteResp[5] & 0x0F;
                EngineName = byteExtractEngineName(REbyteResp);
                }

            if (Setup->WTIU_WiFi_Mode->Checked) {    // in WTIU mode, we don't get an exact number of bytes so we have to look at the command and engine number
                // Handle WTIU ASCII Response I10:05;P&LE SW1500#1556;FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,00,00,00,00,00,00,00,00,00,00,02,60,BF,40,00,00,00,00,00,02,CE,38,78,00,0F,FC,00,00,00,00,00,63;01 okay
                // Got a good response
                // Get the info about whether this is a diesel or steam engine, use
                // that info to enable or disable certain switches
                unsigned int i1;
                char cName[17];
                int retv = sscanf(REbyteResp, "I%*d:%x;%[^;]", &i1, cName);   // %x requires &int
                assert(retv == 2);
                iEngType = i1;
                EngineName = AnsiString(cName);
                }
            if (MessagePopup != NULL) {
                delete MessagePopup;
                MessagePopup = NULL;
                }
            // ask for new engine number, exit on Cancel or engine number already in use
            NumberQuery->Caption = "Change Factory Reset Engine Number";
            char * G = Amp(EngineName.c_str());
            NumberQuery->Question->Caption =  String(G);
            free(G);
            NumberQuery->SpinLabel->Caption = "New Engine Number:";
            NumberQuery->NumberSpin->MaxValue = ENGINEMAX;
            NumberQuery->NumberSpin->MinValue = (Setup->AllowZero->Checked? 0 : 1);
            NumberQuery->NumberSpin->Value = 1;	// current engine number
            NumberQuery->NumberSpin->Visible = true;
            #if __BORLANDC__ >= 0x720
            // Defined for CB10.1 C++ programs that use the VCL.
            NumberQuery->PopupMode = pmExplicit;
            NumberQuery->PopupParent = MainForm;
            NumberQuery->Position =  poDesigned;
            #endif
            int retv = NumberQuery->ShowModal();
            if (retv == mrCancel) {
                break;
                }
            // make sure the new engine number slot is empty
            int NewEngineNumber = NumberQuery->NumberSpin->Value;
            if (NewEngineNumber == 0) break;		// no change
            if (EngineData->Cells[ENG_NAME][NewEngineNumber] != "*") {
                ShowMessage("        This Engine Number is in Use        ");
                break;
                }
            //
            EngineData->Cells [ENG_NAME] [NewEngineNumber] = EngineName;  // put it into the EngineData TStringGrid
            EngineData->Cells [TIU_NUMBER] [NewEngineNumber] = TIU;      // put Engine's TIU number into the EngineData TStringGrid
            SetConsistState(NewEngineNumber, ACTIVE);		// set active
            if (EngineNo == (NewEngineNumber)) {
                // if the Engine is selected, we must set a couple of other fields
                NameofEngine->Text = EngineData->Cells [ENG_NAME] [NewEngineNumber];
                FileNamePrefix->Text = NameofEngine->Text;
                }

            // change engine number
            // send Exxx command where xx is the new Engine Number
            RequestyCommand = true; // request a 'y' command at engine number change, in this case, will be "y0" for FACTORYRESET engine
            unsigned char Command_String[MAX_PATH];
            (void)snprintf((char *)Command_String, sizeof Command_String, "E%d", NewEngineNumber + 1);		// build command string
            // NOTE: FACTORYRESET in the command is the Engine Number (zero) to change to NewEngineNumber
            // NOTE: the new Engine Number as digits xxx do matter   (xxx = 2-100 for Engine Numbers 1-99, xxx = 1 for Factory Reset Engine #0)
            // Wait for first 11 characters of response
            //                                      v- FACTORYRESET is engine number zero
            if (!ptrTIUCommThread->Send_String_FIFO(FACTORYRESET, Command_String, TIU, RemoteNo, 11, SHORT_WAIT, NO_RECORD)) {
                DebugMessage("Failed to change Engine number of Factory Reset Engine");
                break;	// don't make any changes
                }

            // Eng Type = 0x00 for Steam
            // Eng Type = 0x05 for Diesel
            // Eng Type = 0x85 for newer PS3 Diesels
            // Eng Type = 0x25 for electric (PCC Car)
            // Eng Type = 0x10 for Big Boy #4014
            // there may be others but I haven't seen them
            if (DEBUGGING) DB->Debug_Print("Found Factory Reset Engine Type : 0x" + IntToHex(iEngType, 2));
            switch(iEngType) {
                case 0x00:
                case 0x10:
                    EngineData->Cells [ENG_TYPE] [NewEngineNumber] = STEAM_ENGINE;
                    break;
                case 0x05:
                case 0x85:
                    EngineData->Cells [ENG_TYPE] [NewEngineNumber] = DIESEL_ENGINE;
                    break;
                case 0x25:
                    EngineData->Cells [ENG_TYPE] [NewEngineNumber] = ELECTRIC_ENGINE;
                    break;
                default:
                    if (DEBUGGING) DB->Debug_Print("Found an unknown engine type of 0x" + IntToHex(iEngType, 2) + ". Setting it to PS2 Diesel");
                    EngineData->Cells [ENG_TYPE] [NewEngineNumber] = DIESEL_ENGINE;
                    EngineData->Cells[ENG_PS][NewEngineNumber] = PS2;
                    break;
                }
            //
			// This is a guess at determining if this is a PS2 or PS3 engine
            //
            byte ival;
            bool bretv = FetchByte(0x50, &ival, NewEngineNumber, TIU);
            if (!bretv)
                ival = 0;           // default
            if (ival == 0x00) {
				EngineData->Cells[ENG_PS][NewEngineNumber] = PS3;
				PSType[NewEngineNumber] = PS3; // information for the PC threads

                } else {
				EngineData->Cells[ENG_PS][NewEngineNumber] = PS2;
				PSType[NewEngineNumber] = PS2; // information for the PC threads
				}
            // clear out cache
            ScalingFactor[NewEngineNumber] = 0.0;
            Found++;
            break;  // finds one engine per execution
            } else {
            // no engines from this TIU, go on to next TIU
            }
        }
    //ShowMessage("    No Factory Reset Engines to Add    ");
    break;
    }   // while(1)

// redraw the control
EngineData->Repaint();
//
Screen->Cursor = OldCursor;		// restore Cursor

#ifdef OLDWAY
not used
if (Found == 0) {
	DebugMessage("Add Factory Reset Engine Complete - No Engines Found");
	} else {
    DebugMessage("Add Factory Reset Engine Complete - Found " + String(Found) + " Engine");
    }
#else
if (MessagePopup != NULL) {
    MessagePopup->Caption = "Information";
    if (Found == 0) {
        MessagePopup->StatusLine->Caption = "Add Factory Reset Engine Complete - No Engines Found";
        } else {
        MessagePopup->StatusLine->Caption = "Add Factory Reset Engine Complete - Found " + String(Found) + " Engine";
        }
    if (DEBUGGING) DB->Debug_Print(MessagePopup->Caption + " : " + MessagePopup->MessageLine->Caption + " - " + MessagePopup->StatusLine->Caption);
    MessagePopup->Repaint();
    Delay(UIPOPUPTIMEOUT);
    }
#endif
//
STOPem->Enabled = false;
if (MessagePopup != NULL) {
    MessagePopup->Hide();
    delete MessagePopup;
    MessagePopup = NULL;
    }
// Reenable the menu item
AddFactoryResetEngine->Enabled = true;
return;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CountActiveEngines(void)
{
ActiveEngineCount->ValueAsInt = 0;
InactiveEngineCount->ValueAsInt = 0;

for (int iEng = 1 ; iEng <= ENGINEMAX ; iEng++) {
    if ((EngineData->Cells[ENG_NAME][iEng - 1] != "*") && (EngineData->Cells[ENG_STATE][iEng - 1] == ACTIVE)) ActiveEngineCount->ValueAsInt++;		// Adjust Counts
    if ((EngineData->Cells[ENG_NAME][iEng - 1] != "*") && (EngineData->Cells[ENG_STATE][iEng - 1] == INACTIVE)) InactiveEngineCount->ValueAsInt++;
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::UpdateEngDataUsers(void)
{
// Update the users of the Engine List:
//
// Quick Ctrls
// PopOutEngineList
// Lashup Engine List
// Active Engine List
//
// if Quick Ctrls is running, update each page's Engine List
for (int iPage = 1; iPage <= QUICKPAGEMAX ; iPage++) { // Quick Controls page 1-5
    if (ptrQuick[iPage - 1] != NULL) {
        TQuickCtrl * pQuick = ptrQuick[iPage - 1];
        pQuick->GetEngData();
    	}
    }
// Refresh the PopOut List
PopOutEngineList->RefreshClick(NULL);

// update LashUp Engine List
LashUp->ReloadButtonClick(NULL);

//CKR - put any active engines in the active engine StringGrid
FillActEngLst();
}
//---------------------------------------------------------------------------

int __fastcall TMainForm::QueryConsistState(int Engine)
{
// Return the active/inactive state of an Engine on a TIU
//
if (Engine == 0) return (ACTIVE);	// factory reset engine not allowed
assert((Engine >= 1) && (Engine <= ENGINEMAX));	// 1-99
return(EngineData->Cells[ENG_STATE][Engine].ToIntDef(ACTIVE));
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::SetConsistState(int Engine, int State)
{
// set the active/inactive state of an Engine on a TIU
//
assert((State == ACTIVE) || (State == INACTIVE));
if (Engine == 0) return;	// factory reset engine not allowed
assert((Engine >= 1) && (Engine <= ENGINEMAX));	// 1-99
EngineData->Cells[ENG_STATE][Engine] = State;	// set active or inactive
// redraw the control
EngineData->Repaint();
CountActiveEngines();   // update Counts done once here
return;
}
//---------------------------------------------------------------------------

int __fastcall TMainForm::OneBitsinBinaryString(String BitString)
{
// Count the number of '1' bits in a Binary string
int Answer = 0;
const int Len = BitString.Length();
// char& __fastcall operator [](const int idx) returns the character in the string at index value idx.
// The [] operator assumes a base index of 1
for (int i = 1 ; i <= Len ; i++) {
	if (BitString[i] == '1' ) Answer ++;	// [] returns a char
	}
return Answer;
}
//---------------------------------------------------------------------------

int __fastcall TMainForm::OneBitsinHexString(String BitString)
{
assert(false);// this routine not used anymore
// Count the number of '1' bits in a Hex string
int Answer = 0;
const int Len = BitString.Length();
// The [] operator of an AnsiString or String assumes a base index of 1
for (int i = 1 ; i <= Len ; i++) {
	switch(toupper(BitString.c_str()[i])) {
			case '0':
				Answer += 0;
				break;
			case '1':
			case '2':
			case '4':
   			case '8':
				Answer += 1;
				break;
			case '3':
			case '5':
			case '6':
   			case '9':
   			case 'A':
   			case 'C':
				Answer += 2;
                break;
   			case '7':
   			case 'B':
   			case 'D':
   			case 'E':
				Answer += 3;
                break;
   			case 'F':
   				Answer += 4;
                break;
		}
	}
return Answer;
}
//---------------------------------------------------------------------------

String __fastcall TMainForm::sExtractEngineName(String Cmd)
{
// extract the Engine Name
String EngineNamefromTIU = sEncodedStringToASCIIString(Cmd);
return EngineNamefromTIU.SubString(7,16);	// starting at character 7 for 16 characters
// Note: character 6 in the string contains the engine number
}
//---------------------------------------------------------------------------

String __fastcall TMainForm::sEncodedStringToASCIIString(String Cmd)
{
// decode the encoded string into an String
const int SLen = Cmd.Length();
String DecodedString = "";
//  Decode
    for (int idx = 1 ; idx < SLen; idx += 4) {
		if (SLen - idx < 3) continue; // don't decode unless we have 4 characters
		String sVal = bintodec8(bitswapRX(Convert16B8BNumber(Cmd.SubString(idx, 4))));
        int iVal = sVal.ToInt();
    	// print string byte in ASCII
    	String A_Char;
        if (iVal >= 32 && iVal < 128) {   // include SPACE and all characters up to 128
	        A_Char = (char)iVal;  // Convert to ASCII Character
			DecodedString = DecodedString + A_Char;
            } else {
            DecodedString += " ";
            }
        }
return DecodedString;
}
//---------------------------------------------------------------------------

String __fastcall TMainForm::sEncodedStringToBinaryString(String Cmd)
{
// decode the encoded string into binary
const int SLen = Cmd.Length();
String DecodedString = "";
//  Decode
	for (int idx = 1 ; idx < SLen; idx += 4) {
		if (SLen - idx < 3) continue; // don't decode unless we have 4 characters
		String sVal = bitswapRX(Convert16B8BNumber(Cmd.SubString(idx, 4)));
    	DecodedString += sVal;
        }
return DecodedString;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ESTOPClick(TObject *Sender)
{
EmergencyStopAllEngines();	// Stop Engines and take down windows
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CMEmergencyStop(TMessage &Message)
{
// process Emergency Stop message unless disabled
if (Setup->DisableEmergencyStopButton->Checked) return;
//
EmergencyStopAllEngines();
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::EmergencyStopAllEngines(void)
{
if (bEmergencyStop) {	// if Emergency stop already sent, don't resend until disconnect
	return;
	}
// playback might be running so stop it
//
bPlayingBack = bPlaybackCommError = false;	// in one second, the timer will be stopped and
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }

// clear the dispatchlist
if (ptrDL_Thread) {
	ptrDL_Thread->ClearDispatchList();
    }
// Note: EngineNo is not used in the command and could possibily be set to FACTORYRESET    
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner but I don't think the command cares
// emergency Stop command (power off) to all Active TIU, no wait for OK response or timeout
for (int MyTIUNo = 1 ; MyTIUNo <= TIUMAX ; MyTIUNo++) {
    if (TIUSelection[MyTIUNo - 1] == true) {
        (void) ptrTIUCommThread->Send_String_FIFO(EngineNo, "o0", MyTIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK);
        }
    }

SetEvent(TerminationPBEvent);  	// stop any (and all) Playback threads
								// PBFileThread from Setup_Controls.cpp and from Hot_Com.cpp
// stop the Program Control Threads
for (int iPCNo = 1 ; iPCNo <= PCMAX ; iPCNo++) {
	if (ptrProgram_Control[iPCNo-1] != NULL && ptrProgram_Control[iPCNo-1]->ptrPC_Thread != NULL) {
		(void) ptrProgram_Control[iPCNo-1]->ptrPC_Thread->StopRequest();		// Thread will free memory on terminate
        ptrProgram_Control[iPCNo-1]->StopButtonOp->Visible = false;				// remove STOP button on Program Control window
    	}
    }

// clear the dispatchlist
if (ptrDL_Thread) {
	ptrDL_Thread->ClearDispatchList();
    }

// set global flag so that another Emergency Stop is not sent until disconnect occurs
bEmergencyStop = true;
if (DEBUGGING) DB->Debug_Print("Emergency Stop");
(void) Application->MessageBox(TEXT("Emergency Stop : Cycle Power on the WTIU/TIU and then restart RTC"), Caption.c_str(), MB_OK | MB_ICONSTOP);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::AIUCountButtonClick(TObject *Sender)
{
// This function uses the TIU Spinner
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }

if (Setup->Radio_COM_Mode && Setup->TXOnlyComm->Checked) {	// cannot do this function without full duplex communications
	DebugMessagePopup("Warning", MainForm->TXMsg, "");
    AIUSpin->ValueAsInt = 0;
    // AIUButton->Enabled = false;
    AIUSpin->MaxValue = AIUMAX;	// allow all AIU - TXOnlyComm mode
    AIUCount[TIUSpin->ValueAsInt - 1] = AIUMAX;	// allow all AIU - TXOnlyComm mode
    AIUSpin->Enabled = true;
    return;
    }
(void) GetTIUNoAIUCount(TIUSpin->ValueAsInt);	// Get the AIU Count
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::TIUVerButtonClick(TObject *Sender)
{
// This function uses the TIU Spinner
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
if (Setup->Radio_COM_Mode && Setup->TXOnlyComm->Checked) {	// cannot do this function without full duplex communications
	DebugMessagePopup("Warning", MainForm->TXMsg, "");
   	TIUVerButton->Caption = "0.0.0";
    return;
    }
// if selected TIU is not connected fail
if (!TIUSelection[TIUSpin->ValueAsInt - 1]) {
	DebugMessagePopup("Warning", MainForm->TIUActiveMsg, "");
	return;
    }    
(void) GetTIUVer(TIUSpin->ValueAsInt); 	// Get the TIU Version Number
}
//---------------------------------------------------------------------------

bool __fastcall TMainForm::GetAccDecValues(int Engine, int TIU, int * iAcc, int * iDec)
{
if (TIU == 0) return true;  // Engine not found on any active TIU
assert (TIU >= 1 && TIU <= TIUMAX);
bool retv;
int iTIUCount;
if (Setup->Radio_COM_Mode && Setup->TXOnlyComm->Checked) {	// Two way communications required
	if (Setup->StandardAccDec->Checked) {
	    *iAcc = 1; *iDec = 1;
    	} else {
	    *iAcc = 4; *iDec = 2;
        }
    return true;
    }
//
if (Engine == ALLENGINE) {  // in All Engine Mode, force standard acc/dec to all engines
    *iAcc = 1; *iDec = 1;
    return true;
    }
byte ADbyteResp[CMDMAX]; 	// Array for decoded binary response
bool bretv = ptrTIUCommThread->Send_String_FIFO(Engine, "Dq", TIU, RemoteNo, 17, MED_WAIT, NO_RECORD, NULL, NULL, &iTIUCount, ADbyteResp);
// pull out the digits from the Response
if (Setup->Radio_COM_Mode->Checked) {
    // Handle TIU Binary Response   // this command seems to return a response code of 0x00 so that is not an error indication
    // iTIUCount == 17
    // A969 5A6A 6656 5555 5655 5559 5655
    //                          Acc  Dec
    if (bretv) {
        *iAcc = ADbyteResp[5];
        *iDec = ADbyteResp[6];
        } else {
        if (Setup->StandardAccDec->Checked) {
            *iAcc = 1; *iDec = 1;
            } else {
            *iAcc = 4; *iDec = 2;
            }
        DebugMessage("  Get Acc/Dec Rates Failed (Using default values)  ");
        retv = false;
        return retv;
        }
    }

if (Setup->WTIU_WiFi_Mode->Checked) {    // WTIU_WiFi_Mode
    // Handle WTIU ASCII Response - Note: Does not include "okay"
    // iTIUCount == 12
    //Dq:04,02
    //   ^^      <---- Acc  -- returned by the TIU
    //      ^^   <---- Dec
    int a1, d1;
    int retv = sscanf(ADbyteResp, "Dq:%d,%d", &a1, &d1);
    if (retv == 2) {
        *iAcc = a1;
        *iDec = d1;
        } else {
        if (Setup->StandardAccDec->Checked) {
            *iAcc = 1; *iDec = 1;
            } else {
            *iAcc = 4; *iDec = 2;
            }
        DebugMessage("  Get Acc/Dec Rates Failed (Using default values)  ");
        retv = false;
        return retv;
        }
    }
if (DEBUGGING) DB->Debug_Print("GetAccDecValues: Acc/Dec = " + String(*iAcc) + "/" + String(*iDec));
retv = true;
return retv;
}
//---------------------------------------------------------------------------

bool __fastcall TMainForm::GetTIUNoAIUCount(int TIU)
{
//
// Radio Response - 8 bit binary in AIUbyteResp[5], AIU count (0-5)
// WTIU Response - "x01"  (no "okay") in AIUbyteResp, first byte is TIU# - 1 (0-4), LO byte is AIU count (0-5)
//                 "x23" is TIU Number 3, and AIU count = 3
// return true on success
// sets global AIUNo and AIUSpin->ValueAsInt with the AIU Count
// For Radio modes, ignore the TIUNo
// TODO : For WiFI modes, what do we do with the TIU Number?
//
if (TIU == 0) return true;  // Engine not found on any active TIU
assert (TIU >= 1 && TIU <= TIUMAX);
String sAIUVal;
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number

int iTIUCount = -1;
int iTIU = 0, iAIU = 0;
bool retv;
byte AIUbyteResp[CMDMAX]; 	// Array for decoded binary response
AIUbyteResp[0] = NULL;
int respCode = UNDETERMINED;
RequestyCommand = true;     // request a 'y' command at first connection
// Note: EngineNo is not used in the command and could possibily set to FACTORYRESET
ptrTIUCommThread->Send_String_FIFO(EngineNo, "x", TIU, RemoteNo, 15, SHORT_WAIT * 5, NO_RECORD, NULL, &respCode, &iTIUCount, AIUbyteResp); 	// send 'x' command to the TIU
//
// This command seems to return a response code of 0x00 (Radio Modes) so that is not an error indication
// This sets the return value from Send_String_FIFO to false so we can't use it
// Use TIUCount instead
if (iTIUCount == 15 || iTIUCount == 7) {
    // Got the right number of bytes
   	// pull out the value
    if (Setup->Radio_COM_Mode->Checked) {
	    // A9A9 956A 5666 5555 5556 5556
        //                     ^^^^      Number of bytes returned, in this case 1 byte
        //                          ^^^^ AIU  Count -- returned by the TIU
        iAIU = AIUbyteResp[5] & 0x0F;
        sAIUVal = String(iAIU);
        iTIU = TIU; // Radio mode does not return a TIU number
        }

    if (Setup->WTIU_WiFi_Mode->Checked) {
        // Handle ASCII WTIU Response  - Note that this response does NOT have an "okay" appended by the WTIU
        //x01
        // ^  TIU Number minus 1 -- returned by the TIU
        //  ^ AIU  Count -- returned by the TIU

        int retv = sscanf(AIUbyteResp, "x%1d%1d", &iTIU, &iAIU);
        if (retv == 2) {
            sAIUVal = String(iAIU);
            // adjust TIU number for 1-base and set it as current TIU
            iTIU++;
            assert(iTIU == TIU);
            // effectively this does not change the TIU number
            } else {
            bTIUConnected = false;	// indicate problem in communications with TIU
            ConnectedTIULED->Checked = false;
            MainForm->TIUSpinChange(NULL);	// set TIU and AIU Spinner color
            return false;
            }
        }

    // watchout for EConvertError
    try {
    	AIUCountButton->Caption = "AIU(s): " + sAIUVal;
    	if (DEBUGGING) DB->Debug_Print(AIUCountButton->Caption);
        iAIU = sAIUVal.ToInt();	// Throws an error if we can't read the AIU Count
		AIUCount[TIU - 1] = iAIU;
        }
    catch(...) {
        bTIUConnected = false;	// indicate problem in communications with TIU
        if (DEBUGGING) ptrTIUCommThread->PrintItem("GetTIUNoAIUCount: Read AIU Count from TIU " + String(TIU) + " Invalid Value");
        retv = false;
        iAIU = 0;
        AIUCount[TIU - 1] = iAIU;
        iTIU = 0;
        }
    if (iAIU == 0) {
    	AIUSpin->ValueAsInt = 0;	// no AIU present
        AIUSpin->Enabled = false;
        AIUSpin->MaxValue = 0;		// GetTIUNoAIUCount() no AIU present
    	} else {
        // some AIU present
        AIUSpin->ValueAsInt = 1;    // select first AIU
        AIUSpin->Enabled = true;
        AIUSpin->MaxValue = iAIU;	// GetTIUNoAIUCount() max value from 'x' command
        /*
        if (AIUNo == 0) {
        	AIUNo = 1;	// if current AIU is 0, then set it to one
            AIUSpin->ValueAsInt = AIUNo;
            }
        if (AIUNo > iAIU) {
            AIUNo = iAIU;
        	AIUSpin->ValueAsInt = AIUNo;	// reset AIU number if too high
            }
        */
        }
    if (AIUSpin->ValueAsInt == 0) {
    	AIUSpin->Enabled = false;	// no AIU present
        AIUSpin->ColorDefault = clInactiveBorder;
        } else {
    	AIUSpin->Enabled = true;	// some AIU present
	    AIUSpin->ColorDefault = clLime;
        }
	retv = true;
    } else {
    // Read AIU count failed
    if (DEBUGGING) ptrTIUCommThread->PrintItem("GetTIUNoAIUCount: Read AIU Count from TIU " + String(TIU) + " Failed");
    DebugMessage("Read AIU Count from TIU " + String(TIU) + " Failed");
    // no AIU
    AIUSpin->ValueAsInt = 0;	// no AIU
    iAIU = 0;
    // AIUButton->Enabled = false;
    AIUSpin->MaxValue = AIUMAX;	// GetTIUNoAIUCount() Read AIU count failed so allow all AIU
    AIUSpin->Enabled = true;
    bTIUConnected = false;	// indicate problem in communications with TIU
    ConnectedTIULED->Checked = false;
    MainForm->TIUSpinChange(NULL);	// set TIU and AIU Spinner color
    retv = false;
    return retv;
    }
//
//AIUCount[TIU - 1] = iAIU;  // GetTIUNoAIUCount() max value from 'x' command
if (DEBUGGING && retv == true) ptrTIUCommThread->PrintItem("GetTIUNoAIUCount : TIU# = " + String(iTIU) + " Number of AIU = " + String(iAIU));
return retv;
}
//---------------------------------------------------------------------------

bool __fastcall TMainForm::GetTIUVer(int TIU)
{
//
// Radio Response - 4 characters in TIUbyteResp[5]-[8]
// WIU\TIU Response - "!30 36 31 30  okay" in TIUbyteResp
// WTIU early Response - v1.03-0-f0ee3b4c okay
// WTIU later Response - v1.3.0-20250814-1255
//
if (TIU == 0) return true;  // Engine not found on any active TIU
assert (TIU >= 1 && TIU <= TIUMAX);
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
int iTIUCount = -1;
int respCode = UNDETERMINED;
String D1, D2, D3, D4;
byte TIUbyteResp[CMDMAX] = "\0'"; 	// Array for decoded binary response
// Note: EngineNo is not used in the command and could possibily set to FACTORYRESET
bool retv = ptrTIUCommThread->Send_String_FIFO(EngineNo, "!", TIU, RemoteNo, 21, SHORT_WAIT, NO_RECORD, NULL, &respCode, &iTIUCount, TIUbyteResp); 	// send '!' command to the TIU
//
// This command may return a response code of 0x00 (Radio Modes) so that is not an error indication
// This sets the return value from Send_String_FIFO to false so we can't use it
// Use TIUCount instead
if (iTIUCount == 21 || iTIUCount == 22 || iTIUCount > 22) {
	// watch out for EConvertError
   	// pull out the digits
    try {
        if (Setup->Radio_COM_Mode->Checked) {
            //5AA5 969A 5A5A 5555 5559 6565 6569 6665 6565
            //                    ^^^^                      Number of bytes in response : 4
            //                          '0'  '4'  '2'  '0' 	<- characters returned by v4.20
            //66A5 9969 5A5A 5555 5559 6565 6569 6666 5555
            //                          '0'  '4'  '3'  \0 	<- characters returned by v4.30
            D1 = (char) TIUbyteResp[5];
            D2 = (char) TIUbyteResp[6];
            D3 = (char) TIUbyteResp[7];
            D4 = (char) TIUbyteResp[8];

            // NOTE: a bug in the TIU 4.30 software causes this response to come
            // out "04.3 ". Earlier version shows correctly as "04.20" - Verified
            // with Remote
            // The next line fixes the error so it is displayed correctly
            if (D4.IsEmpty() || D4 == " ") D4 = "0";
            // delete leading zero
            if (D1 == "0") D1 =   " ";
            TIUVerButton->Caption = D1 + D2 + "." + D3 + D4;
            TIUVerButton->Hint = "Read up Version Number from the TIU";
            if (DEBUGGING) DB->Debug_Print(TIUVerButton->Caption);
            }

		if (Setup->WTIU_WiFi_Mode->Checked) {
            // Handle WTIU ASCII Response
            // pull out the digits - WIU Format
            //!30 36 31 30  okay
            // ^^ ^^ ^^ ^^  <---- TIU Version -- returned by the TIU and WIU/TIU
            // pull out the version string - Two different WTIU Formats
            //!v1.03-0-f0ee3b4c okay\r\n->
            //  ^^^^         <---- TIU Version -- returned by the WTIU
            //!v1.3.0-20250814-1255 okay\r\n->
            //  ^^^^^        <---- TIU Version -- returned by the WTIU
            int c1, c2, c3, c4;
            D1 = D2 = D3 = D4 = "";
			int retv = sscanf(TIUbyteResp, "!%x %x %x %x", &c1, &c2, &c3, &c4);   // %x requires &int
			if (retv == 4) {    // TIU and WIU/TIU format version number
				D1 = (char) c1;
                D2 = (char) c2;
                D3 = (char) c3;
                D4 = (char) c4;
                TIUVerButton->Caption = D1 + D2 + "." + D3 + D4;
                if (DEBUGGING) DB->Debug_Print(TIUVerButton->Caption);
                } else {        // WTIU format version number
                char VerChar[25];
                int retv = sscanf(TIUbyteResp, "!v%[0-9.]", &VerChar);   // %c requires &int
                if (retv == 1 ) {
                    TIUVerButton->Caption = VerChar;
                    TIUVerButton->Hint = String((char *)TIUbyteResp);
                    if (DEBUGGING) DB->Debug_Print(TIUVerButton->Caption);
                    }
                }
            }
        //
        bTIUConnected = true;	// indicate successful communications with the TIU
        ConnectedTIULED->Checked = true;
        MainForm->TIUSpinChange(NULL);	// set TIU and AIU Spinner color
        retv = true;
        }
    catch(...) {
        DebugMessage("Read TIU Version Number from TIU " + String(TIU) + " Failed");
        TIUVersion[TIU - 1] = 0;
        bTIUConnected = false;	// indicate problem in communications with TIU
        ConnectedTIULED->Checked = false;
        retv = false;
    	}
    } else {
    // Read TIU Version number Failed
    DebugMessage("Read TIU Version Number Failed from TIU " + String(TIU));
    bTIUConnected = false;	// indicate problem in communications with TIU
    ConnectedTIULED->Checked = false;
    MainForm->TIUSpinChange(NULL);	// set TIU and AIU Spinner color
    retv = false;
    }
return retv;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ADButtonClick(TObject *Sender)
{
//
// Called by the button in Debug mode only
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
if (Setup->Radio_COM_Mode && Setup->TXOnlyComm->Checked) {	// cannot do this function without full duplex communications
	DebugMessagePopup("Warning", MainForm->TXMsg, "");
    return;
    }
AccLabel->Caption = "Acc : ?";
DecLabel->Caption = "Dec : ?";
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
assert(EngineNo >= (Setup->AllowZero->Checked? 0 : 1) && EngineNo <= ENGINEMAX);	// 0 factory reset, 1-99
int MyTIUNo = EngineData->Cells[TIU_NUMBER] [EngineNo].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
    return;
    }
//
int iAcc, iDec;	// use return values only if communications is successful
if (GetAccDecValues(EngineNo, MyTIUNo, &iAcc, &iDec)) {         // Debug button push
	AccLabel->Caption = "Acc : " + String(iAcc) + " Smph/s";	// change with DaXX, XX = 1,10
	DecLabel->Caption = "Dec : " + String(iDec) + " Smph/s";	// change with DdXX
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::EditEngineNameClick(TObject *Sender)
{
// Engine Data popup menu item
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
assert(EngineNo >= (Setup->AllowZero->Checked? 0 : 1) && EngineNo <= ENGINEMAX);	// 0/1-99
// You can edit the engine name even if the TIU is not connected.
// This lets you change the name in the data base and how it appears
// in the engine list
#ifdef OLDWAY
not used
// if TIU Port is not connected do nothing
if (!IsTIUConnected(TIU)) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
#endif
// Prepare the Engine Name field for editing
NameofEngine->Cursor = crHandPoint;
NameofEngine->ReadOnly = false;
NameofEngine->SetFocus();
NameofEngine->Hint = "After selecting from the menu, use this field to edit the Engine Name - Press [Enter] to send to engine";

SavedNameofEngine = NameofEngine->Text; // save original engine name in case of failure
return;

#ifdef OLDEDITCODEMOVED
/*// Edit Engine Name
// NewEngineName must be AnsiString because the c_str() taken needs to be a char type
AnsiString NewEngineName = InputBox("Edit Engine #" + String(EngineNo) + " Name", "Only the first 16 characters will be sent to the Engine:", NameofEngine->Text);
if (NewEngineName == NameofEngine->Text) return;	// no change
// must be more than 1 character
if (NewEngineName.Length() == 1) {
	ShowMessage("New Engine Name must be more than one character");
    return;	// no change
    }
//
if (EngineNo > 0) EngineData->Cells[ENG_NAME][EngineNo] = NewEngineName.Trim();
// send new engine name to engine
NameofEngine->Text = NewEngineName.Trim();
FileNamePrefix->Text = NameofEngine->Text;

// send Command with exactly 16 characters for new Engine name
unsigned char Command_String[MAX_PATH];
// Limit engine name sent to TIU to exactly 16 characters
RequestyCommand = true; // request a 'y' command at engine name change
snprintf((char *)Command_String, sizeof Command_String, "Ns%-16.16s", NewEngineName.c_str());	// exactly 16 characters
if (!ptrTIUCommThread->Send_String_FIFO(EngineNo, Command_String, TIUNo, RemoteNo, 11, LONG_WAIT, NO_RECORD)) {
	ShowMessage("Failed to set new Engine name (Engine " + String(EngineNo) + ")");
    return;
    }
SetConsistState(EngineNo, TIUNo, ACTIVE);		// set active
*/
#endif
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::EngineDataPopupMenuPopup(TObject *Sender)
{
// Perform right click function then bring up the popup menu
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
if ((EngineNo == 0) && (Setup->AllowZero->Checked == false)) return;

int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
    return;
    }
//
if (EngineNo <= ENGINEMAX) {
    if (!ActEngChckBox->Checked) {
        // When showing the complete Engine List, use these settings
        //
    EditEngineName->Caption = "Edit Engine #" + String(EngineNo) + " Name - TIU " + String(MyTIUNo);
    EditEngineNumber->Caption = "Edit Engine #" + String(EngineNo) + " Number - TIU " + String(MyTIUNo);
    EditTIUNumber->Caption = "Change TIU Number Associated with Engine #" + String(EngineNo);
    ShowWatchdogMenuItem->Caption = "Show Watchdog Button - TIU " + String(MyTIUNo);
    DeleteEngine->Caption = "Delete Engine #" + String(EngineNo);
    SetEngineintoCommandMode->Caption = "Set Engine #" + String(EngineNo) + " into Command Mode";
    SetEngineintoLegacyMode->Caption = "Set Engine #" + String(EngineNo) + " into Legacy Mode";
    SetEngineintoConventionalMode->Caption = "Set Engine #" + String(EngineNo) + " into Conventional Mode";
    SetEngineActive1->Caption = "Set Engine #" + String(EngineNo) + " Active";
    SetEngineInactive1->Caption = "Set Engine #" + String(EngineNo) + " Inactive";
    EngineInformation1->Caption = "Engine #" + String(EngineNo) + " Information - TIU " + String(MyTIUNo);
    EngineTrackSignal1->Caption = "Engine #" + String(EngineNo) + " Track Signal (ping) - TIU " + String(MyTIUNo);
    EngineStatus1->Caption = "Engine #" + String(EngineNo) + " Status - TIU " + String(MyTIUNo);
    SetEnginePS2->Caption = "Set Engine #" + String(EngineNo) + " as PS2";
    SetEnginePS2->Checked = (MainForm->EngineData->Cells[ENG_PS][EngineNo] == PS2);
    SetEnginePS3->Caption = "Set Engine #" + String(EngineNo) + " as PS3";
    SetEnginePS3->Checked = (MainForm->EngineData->Cells[ENG_PS][EngineNo] == PS3);
    FactoryReset1->Caption = "Factory Reset Engine #" + String(EngineNo) + " - TIU " + String(MyTIUNo);
    FeatureReset1->Caption = "Feature Reset Engine #" + String(EngineNo) + " - TIU " + String(MyTIUNo);
    EditEngineName->Visible = true;
    EditEngineNumber->Visible = true;
    EditTIUNumber->Visible = true;
    DeleteEngine->Visible = true;
    SetEngineintoCommandMode->Visible = true;
    SetEngineintoLegacyMode->Visible = true;
    SetEngineintoConventionalMode->Visible = true;
    SetEngineActive1->Visible = true;
    SetEngineInactive1->Visible = true;
    EngineInformation1->Visible = true;
    EngineTrackSignal1->Visible = true;
    EngineStatus1->Visible = true;
    SetAllEnginesActive->Visible = true;
    SetAllEnginesInactive->Visible = true;
    SetEnginePS2->Visible = true;
    SetEnginePS3->Visible = true;
    FactoryReset1->Visible = true;
    FeatureReset1->Visible = true;
    } else {
        // When showing the Active Engine List, use these settings
        //
        //EditEngineName->Caption = "Edit Engine #" + String(EngineNo) + " Name - TIU " + String(MyTIUNo);
        //EditEngineNumber->Caption = "Edit Engine #" + String(EngineNo) + " Number - TIU " + String(MyTIUNo);
        //EditTIUNumber->Caption = "Change TIU Number Associated with Engine #" + String(EngineNo);
        ShowWatchdogMenuItem->Caption = "Show Watchdog Button - TIU " + String(MyTIUNo);
        //DeleteEngine->Caption = "Delete Engine #" + String(EngineNo);
        //SetEngineintoCommandMode->Caption = "Set Engine #" + String(EngineNo) + " into Command Mode";
        //SetEngineintoLegacyMode->Caption = "Set Engine #" + String(EngineNo) + " into Legacy Mode";
        //SetEngineintoConventionalMode->Caption = "Set Engine #" + String(EngineNo) + " into Conventional Mode";
        //SetEngineActive1->Caption = "Set Engine #" + String(EngineNo) + " Active";
        //SetEngineInactive1->Caption = "Set Engine #" + String(EngineNo) + " Inactive";
        EngineInformation1->Caption = "Engine #" + String(EngineNo) + " Information - TIU " + String(MyTIUNo);
        EngineTrackSignal1->Caption = "Engine #" + String(EngineNo) + " Track Signal (ping) - TIU " + String(MyTIUNo);
        EngineStatus1->Caption = "Engine #" + String(EngineNo) + " Status - TIU " + String(MyTIUNo);
        //SetEnginePS2->Caption = "Set Engine #" + String(EngineNo) + " as PS2";
        //SetEnginePS2->Checked = (MainForm->EngineData->Cells[ENG_PS][EngineNo] == PS2);
        //SetEnginePS3->Caption = "Set Engine #" + String(EngineNo) + " as PS3";
        //SetEnginePS3->Checked = (MainForm->EngineData->Cells[ENG_PS][EngineNo] == PS3);
        //FactoryReset1->Caption = "Factory Reset Engine #" + String(EngineNo) + " - TIU " + String(MyTIUNo);
        //FeatureReset1->Caption = "Feature Reset Engine #" + String(EngineNo) + " - TIU " + String(MyTIUNo);
        EditEngineName->Visible = false;
        EditEngineNumber->Visible = false;
        EditTIUNumber->Visible = false;
        DeleteEngine->Visible = false;
        SetEngineintoCommandMode->Visible = false;
        SetEngineintoLegacyMode->Visible = false;
        SetEngineintoConventionalMode->Visible = false;
        SetEngineActive1->Visible = false;
        SetEngineInactive1->Visible = false;
        EngineInformation1->Visible = true;
        EngineTrackSignal1->Visible = true;
        EngineStatus1->Visible = true;
        SetAllEnginesActive->Visible = false;
        SetAllEnginesInactive->Visible = false;
        SetEnginePS2->Visible = false;
        SetEnginePS3->Visible = false;
        FactoryReset1->Visible = false;
        FeatureReset1->Visible = false;
        }
    } else {
    // use these settings for engine number out of range
    EditEngineName->Visible = false;
    EditEngineNumber->Visible = false;
    EditTIUNumber->Visible = false;
    DeleteEngine->Visible = false;
    SetEngineintoCommandMode->Visible = false;
    SetEngineintoLegacyMode->Visible = false;
    SetEngineintoConventionalMode->Visible = false;
    SetEngineActive1->Visible = false;
    SetEngineInactive1->Visible = false;
    EngineInformation1->Visible = false;
    EngineTrackSignal1->Visible = false;
    EngineStatus1->Visible = false;
    SetAllEnginesActive->Visible = false;
    SetAllEnginesInactive->Visible = false;
    SetEnginePS2->Visible = false;
    SetEnginePS3->Visible = false;
    FactoryReset1->Visible = false;
    FeatureReset1->Visible = false;
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::EditTIUNumberPopupMenuPopup(TObject *Sender)
{
//
ShowWatchdogTIUSpinnerMenuItem->Caption = "Show Watchdog Button - TIU " + String(TIUSpin->ValueAsInt);
DCSSetupforSelectedTIU1->Caption = "DCS Setup for TIU " + String(TIUSpin->ValueAsInt);
DisplayVar1->Caption = "Show Track Control V1 Operations Window - TIU " + String(TIUSpin->ValueAsInt);
DisplayVar2->Caption = "Show Track Control V2 Operations Window - TIU " + String(TIUSpin->ValueAsInt);
TIUFactoryReset1->Caption = "Factory Reset - TIU " + String(TIUSpin->ValueAsInt);
TIUFeatureReset1->Caption = "Feature Reset - TIU " + String(TIUSpin->ValueAsInt);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::EditEngineNumberClick(TObject *Sender)
{
// Engine Data popup menu item
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
assert(EngineNo >= (Setup->AllowZero->Checked? 0 : 1) && EngineNo <= ENGINEMAX);	// 0/1-99
// if TIU Port is not connected do nothing
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
// change the engine number - allowing changing the engine number for engine #0
if ((EngineNo != 0) && (EngineData->Cells[ENG_NAME][EngineNo] == "*")) {
	ShowMessage("         No Engine with this number         ");
	return;
    }

NumberQuery->Caption = "Edit Engine #" + String(EngineNo) + " Number";
NumberQuery->Question->Caption = "Select the New Engine Number";
NumberQuery->SpinLabel->Caption = "New Engine Number:";
NumberQuery->NumberSpin->MaxValue = ENGINEMAX;
NumberQuery->NumberSpin->MinValue = (Setup->AllowZero->Checked? 0 : 1);
NumberQuery->NumberSpin->Value = EngineNo;	// current engine number
NumberQuery->NumberSpin->Visible = true;
#if __BORLANDC__ >= 0x720
// Defined for CB10.1 C++ programs that use the VCL.
NumberQuery->PopupMode = pmExplicit;
NumberQuery->PopupParent = MainForm;
NumberQuery->Position =  poDesigned;
#endif
int retv = NumberQuery->ShowModal();
if (retv == mrCancel) return;
// make sure the new engine number slot is empty
int NewEngineNumber = NumberQuery->NumberSpin->Value;
if (EngineNo == NewEngineNumber)return;		// no change
if ((NewEngineNumber != 0) && (EngineData->Cells[ENG_NAME][NewEngineNumber] != "*")) {
	ShowMessage("        This Engine Number is in Use        ");
	return;
    }

// Shutdown the old Engine
ShutdownButton->OnClick(ShutdownButton); 	// closes Op and Volume Forms if they exist
Delay(1 * 1000);
// send Exxx command where xx is the new Engine Number
// Old or New engine number can be 0 which would indicate that it was a factory reset engine
RequestyCommand = true;  // request a 'y' command at engine number change
unsigned char Command_String[MAX_PATH];
(void)snprintf((char *)Command_String, sizeof Command_String, "E%d", NewEngineNumber + 1);		// build command string
// NOTE: EngineNo in the command is the Engine to change to NewEngineNumber
// NOTE: the Engine Number as digits xxx do matter   (xxx = 2-100 for Engine Numbers 1-99)
// Wait for first 11 characters of response
int MyTIUNo = EngineData->Cells[TIU_NUMBER] [EngineNo].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
    return;
    }
if (!ptrTIUCommThread->Send_String_FIFO(EngineNo, Command_String, MyTIUNo, RemoteNo, 11, SHORT_WAIT, NO_RECORD)) {
	DebugMessage("Failed to change Engine number (Engine " + String(EngineNo) + ")");
    return;	// don't make any changes
    }

// change the global EngineNo, the Engine Name box and the selection on the Engine Data List

// transfer the rows data from EngineNo to NewEngineNumber
if (EngineNo != 0 && (NewEngineNumber != 0)) {
	assert(EngineData->Rows[EngineNo]->Count == ENGDAT_COUNT);
	EngineData->Rows[NewEngineNumber]->CommaText = EngineData->Rows[EngineNo]->CommaText;
	EngineData->Cells[ENG_NUM][NewEngineNumber] = NewEngineNumber; 	// fix the engine number in the StringGrid
    }

// clear out the old Engine Info
if (EngineNo != 0) {
        EngineData->Rows[EngineNo]->CommaText = String(EngineNo) + ENGDAT_INIT;
        assert(EngineData->Rows[EngineNo]->Count == ENGDAT_COUNT);
        }

// clear out cache
PSType[EngineNo] = UDEF0;
ScalingFactor[EngineNo] = 0.0;
PSType[NewEngineNumber] = UDEF0;    // This will cause the cache to be reloaded on next use
ScalingFactor[NewEngineNumber] = 0.0;
// transfer the new engine number
EngineSpin->Value = NewEngineNumber;	// causes EngineNo to change
//
if (EngineNo != 0) {
	NameofEngine->Text = EngineData->Cells[ENG_NAME][EngineNo];
    FileNamePrefix->Text = NameofEngine->Text;
	// highlight the selection
	EngineData->Col = ENG_NAME;
	EngineData->Row = EngineNo;
    EngineUpDown->Position = EngineData->RowCount - EngineNo;
    // Enable buttons if an operations form is created
	if (ptrOp[EngineNo] != NULL) {
		SoundCtrlsButton->Enabled = true;
		OpButton->Enabled = true;
		} else {
		SoundCtrlsButton->Enabled = false;
		OpButton->Enabled = false;
		}
    } else {
    // if EngineNo == 0
    NameofEngine->Text = "Factory Reset Engine";
    FileNamePrefix->Text = NameofEngine->Text;
    }

// update users of the Engine List
UpdateEngDataUsers();
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::DeleteEngineClick(TObject *Sender)
{
// Engine Data popup menu item
// Delete an engine from the Engine List
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
if (EngineData->Cells[ENG_NAME][EngineNo] == "*") {
	ShowMessage("         No Engine with this number         ");
	return;
    }
String AskString = "Do you really want to delete " + EngineData->Cells[ENG_NAME][EngineNo] + "?";
int retv = Application->MessageBox(AskString.c_str(), TEXT("Confirmation"), MB_YESNO | MB_ICONQUESTION);
if (retv == IDNO) return;
// if consist is running, pop up error message
if (IsConsistRunning(EngineNo)) {
	ShowMessage("Shutdown the engine before you try to delete it");
    return;
    }
// otherwise delete it
// then clear out the Engine List
EngineData->Cells[ENG_NAME][EngineNo] = "*";
NameofEngine->Text = "*";
FileNamePrefix->Text = NameofEngine->Text;
SetConsistState(EngineNo, INACTIVE);		// set inactive

// clear out cache
PSType[EngineNo] = UDEF0;
ScalingFactor[EngineNo] = 0.0;

// update users of the Engine List
UpdateEngDataUsers();
}
//---------------------------------------------------------------------------

unsigned int HextoInt(char c)
{
// Convert a Hex digit to a binary number
  if (c >= '0' && c <= '9') return      (c - '0');
  if (c >= 'A' && c <= 'F') return 10 + (c - 'A');
  if (c >= 'a' && c <= 'f') return 10 + (c - 'a');
  return -1;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::FeatureResetButtonClick(TObject *Sender)
{
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
if (EngineNo <= 0 || EngineNo >= ALLENGINE) return;	// Engines only

if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }

if (EngineNo >= ALLENGINE) return;	// can't reset All Engine
// Engine Feature Reset
String FeatureResetMsg = "Are you sure you want to Feature Reset engine " + String(EngineNo) + " - " +
	EngineData->Cells[ENG_NAME][EngineNo] + "?";
int retv = Application->MessageBox(FeatureResetMsg.c_str(),	TEXT("Warning"), MB_YESNO | MB_ICONQUESTION);
if (retv != IDYES) {
    return;
    }
int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
    return;
    }
(void)ptrTIUCommThread->Send_String_FIFO(EngineNo, "F0", MyTIUNo, RemoteNo, 11, VERY_LONG_WAIT, RECORD_OK);
// Feature reset turns Smoke on so you must turn it off if you want it off.
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::FactoryResetButtonClick(TObject *Sender)
{
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
if (EngineNo <= 0 || EngineNo >= ALLENGINE) return;	// Engines only

if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
// Engine Factory Reset
String FactoryResetMsg = "Are you sure you want to Factory Reset engine " + String(EngineNo) + " - " +
	NameofEngine->Text + " (Engine Number will reset to 0)?";
int retv = Application->MessageBox(FactoryResetMsg.c_str(), TEXT("Warning"), MB_YESNO | MB_ICONQUESTION);
if (retv != IDYES) {
    return;
    }
int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
    return;
    }
(void)ptrTIUCommThread->Send_String_FIFO(EngineNo, "F1", MyTIUNo, RemoteNo, 11, VERY_LONG_WAIT * 20, NO_RECORD); 	// don't record this reset

// clear out the old Engine Info including the Name
if (EngineNo != 0) {
        EngineData->Rows[EngineNo]->CommaText = String(EngineNo) + ENGDAT_INIT;
        assert(EngineData->Rows[EngineNo]->Count == ENGDAT_COUNT);
        }
EngineData->Cells[ENG_NAME][EngineNo] = "*";
NameofEngine->Text = "*";
FileNamePrefix->Text = NameofEngine->Text;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::UndoRFIDBufferClick(TObject *Sender)
{
RFIDBuffer->Undo();
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::UndoTIUBufferClick(TObject *Sender)
{
TIUBuffer->Undo();
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::UndoCRCBufferClick(TObject *Sender)
{
CRCBuffer->Undo();
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::I0ButtonClick(TObject *Sender)
{
//
// Debug use only
//
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
if (Setup->Radio_COM_Mode && Setup->TXOnlyComm->Checked) {	// cannot do this function without full duplex communications
	DebugMessagePopup("Warning", MainForm->TXMsg, "");
    return;
    }

// Search for all engines in TIU
byte I0byteResp[CMDMAX]; 	// Array for decoded binary response
int iTIUCount;
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and TIU number in spinner
int MyTIUNo = TIUSpin->ValueAsInt;
// Engine Number can be anything - in the Remote it is FACTORYRESET (0)
bool bretv = ptrTIUCommThread->Send_String_FIFO(EngineNo, "I0", MyTIUNo, RemoteNo, 39, I_WAIT, NO_RECORD, NULL, NULL, &iTIUCount, I0byteResp);
// Returns a 104 bit string (13 bytes) with right most bit being
// engine 1 (a factory reset engine) of the engine number to be added - only need the rightmost 99 bits

if (bretv && (iTIUCount == 39 || iTIUCount == 50)) {	// the I0 command returns 39 or 50 characters
    // I0 Button Click uses OneHundredBitString, J13HexString, ThirtySixHexString and TwentySixHexString
    if (Setup->Radio_COM_Mode->Checked) {
        // Handle Radio Binary Response
        // Got a good response;
        // pull out the 100 Bit long Engine Encoding starting at character 11 for 26 characters
        //  5AA6A9555A5A5555595A5955555555555555555555555555555555555555555556655555 (0x00)
        //    5B  A8  0F  00  0D  08  00  00  00  00  00  00  00  00  00  00  12  00
        //crc ^^  ^^
        //TIU/Remote  ^^
        //response code   ^^
        //length              ^^  (here its 13 bytes)
        //one bit per engine      ^^                   ....
        // pull out the 100 Bit long Engine Encoding starting at character 11 for 26 characters
        // the string is in Hex so we really need two characters to get one 8 bit hex value
        String EncodedBinary = byteBinaryStringToBinaryString(&I0byteResp[5], 13);
        if (DEBUGGING) DB->Debug_Print("Read Engines - EncodedBinary : " + EncodedBinary);
        // 6 means skip the first 5 bits
        // then pick up the next 99 bits
        // TODO : (it's 98 Engines due to the bug in the TIU code when an AIU is attached.)
        OneHundredBitString = "0" + EncodedBinary.SubString(6, 99);  // 100 bits
        //
        String ThirtySixHexString = byteBinaryStringToHexString(I0byteResp, 18);
        assert(ThirtySixHexString.Length() == 36);
        // "J000000000000000000001FFFFE" each bit counting from the right is an engine. Example showing engines 1 through 20
        J13HexString = "J" + ThirtySixHexString.SubString(11, 26);   // 13 hex digits (26 characters)
        }
    if (Setup->WTIU_WiFi_Mode->Checked) {
        // Handle WTIU ASCII Response
        // Handle WTIU ASCII Response   IO:08,00,00,00,00,00,00,00,00,00,00,12,00 okay    (50 chars)
        // pull out the 100 Bit long Engine Encoding
        String EncodedBinary = byteCharacterStringToBinaryString(&I0byteResp[3], 13);
        // 6 means skip the first 5 bits
        // then pick up the next 99 bits
        // TODO : (it's 98 Engines due to the bug in the TIU code when an AIU is attached.(see NOTE below))
        OneHundredBitString = "0" + EncodedBinary.SubString(6, 99);  // 100 bits
        //
        // The format for the 'J' command for the WTIU is the same as for the Radio.
        String TwentySixHexString =  "";
        for(size_t i = 3; i <= 40; i++) {
            if(I0byteResp[i] != ',') {
                TwentySixHexString += String((char)I0byteResp[i]);
                }
            }
        assert(TwentySixHexString.Length() == 26);
        // "J00000000000000000000000122" each bit counting from the right is an engine
        J13HexString = "J" + TwentySixHexString;   // 13 hex digits (26 characters)
        }
    // NOTE NOTE there is a problem with the 100 bits. When an AIU is plugged
    // into the TIU, engine bit DCS #100 (Engine 99) comes on
    // EG:                    .
    //   F4  D0  0F  00  0D  08  00  00  00  00  00  00  00  00  00  00  01  10
    // it's the 08 in the first of the 13 values that make up the 100 bits
             // TODO : Due to error in TIU, if any AIU is connected, then
             // the bit for engine 99 (DCS 100) is on. Need to skip that bit for now
             // that is, check only up to Engine 98 (DCS 99)
	// Count the number of 1 bits on in the Binary string
    int EngCount = OneBitsinBinaryString(OneHundredBitString);
    if (DEBUGGING) {
        DB->Debug_Print("I0 Button - Found " + String(EngCount) + " Engine(s)");
        DB->Debug_Print("I0 Button - OneHundredBitString :  " + OneHundredBitString);
        DB->Debug_Print("I0 Button - J13HexString : " + J13HexString);
        }
    //I0Button->Caption = String(EngCount) + " Engine(s)";
    I0Button->Caption = String(EngCount) + " on TIU#" + MyTIUNo;
    } else {
	// didn't get enough data
    DebugMessagePopup("Warning", "Failed to get Engine list", "");
    }
}
//---------------------------------------------------------------------------
void __fastcall TMainForm::IxxxButtonClick(TObject *Sender)
{
//
// Debug use only
//
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
if (Setup->Radio_COM_Mode && Setup->TXOnlyComm->Checked) {	// cannot do this function without full duplex communications
	DebugMessagePopup("Warning", MainForm->TXMsg, "");
    return;
    }
// Interrogate a single engine and display the results
    byte IxxxbyteResp[CMDMAX];
    byte byteResp[CMDMAX];  // Array for decoded binary response
    int iTIUCount;
    int respCode;			// integer for comm response code
    int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and TIU number in spinner
    int MyTIUNo = TIUSpin->ValueAsInt;
    int EngineType, LastByte;
    char EngineName[17];
	char Command[10];
	(void) snprintf(Command, sizeof Command, "I%d", EngineNo + 1);	// Uses the DCS Engine Number in the "Ixx" command  (Operations Control Constructor)
	bool bretv = ptrTIUCommThread->Send_String_FIFO(EngineNo, Command, MyTIUNo, RemoteNo, 173, I_WAIT, NO_RECORD, NULL, &respCode, &iTIUCount, IxxxbyteResp);
    // Get the info about what switches the engine supports.
	if (bretv && iTIUCount >= 173) {    // Radio is 173 bytes, WiFi is around 220 bytes depending on the # characters in engine name
		if (Setup->Radio_COM_Mode->Checked) {     // in Radio mode, we get 173 bytes if an engine is found
            // Handle Radio COM
			// Got a good response
            // Response to Ixxx command:
            // 996AA5A65A5A555556A5555A55A566595999559A655556A656AA6566656A6565656566566566656A656A6669AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA555555555555555555555555555555555555555556556595AA6A55955555555555555555555556559A99696569A555555A5AA9A955555555555555555556 (0x00)
            //   9D  F1  0F  00  52  05  50  26  4C  45  20  53  57  31  35  30  30  23  31  35  35  36  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  FF  00  00  00  00  00  00  00  00  00  00  02  60  BF  40  00  00  00  00  00  02  CE  38  78  00  0F  FC  00  00  00  00  01
            //                     ^^^^ is is 0x00/0x10 for Steam, 0x05/0x85 for Diesel, and 0x25 Gas/Electric
            // see spreadsheet named "Command ('Ixxx') Response.xls"  for decoding details
            EngineType = IxxxbyteResp[5];
            strncpy(EngineName, &IxxxbyteResp[6], 16);  // will not NULL terminate
            EngineName[16] = '\0';
            LastByte = IxxxbyteResp[84];
            // No format change necessary
            memcpy(byteResp, IxxxbyteResp, CMDMAX);
            // byteResp[] has the decoded response string
            }

		if (Setup->WTIU_WiFi_Mode->Checked) {    // in WTIU mode, we don't get an exact number of bytes because blanks at end of engine name are suppressed
			// The string starts with "I1"
            // Handle WTIU ASCII Response I10:05;P&LE SW1500#1556;FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,FF,00,00,00,00,00,00,00,00,00,00,02,60,BF,40,00,00,00,00,00,02,CE,38,78,00,0F,FC,00,00,00,00,00,63;01 okay
            // Got a good response'
            // parse out the header info
            byte rawbyte[CMDMAX];
            int retv = sscanf(IxxxbyteResp, "I%d:%x;%[^;];%[^;];%x okay", &EngineNo, &EngineType, EngineName, rawbyte, &LastByte);
            assert(retv == 5);
            // Convert each character hex value ("FF") to hex (0xff)and put into byteResp
            byteCharacterStringTobyteHexString(rawbyte, &byteResp[22], 64);
            // byteResp[] has the decoded response string
            }
       }
    /* Format() did not handle the %02X by inserting leading zeros. sprintf() did. Failed in
        both BCB4.0 and CB10.1
        The two formats: %02X and %.2X should produce the same result but with Format() they did not
        produce the leading zero.
    DB->Debug_Print(Format((String)"Engine Number %d - Engine Type 0x%02X - Engine Name \"%s\" - Last Byte 0x%.2X",
        ARRAYOFCONST((EngineNo, EngineType, EngineName, LastByte))), clBlue);
        */
    char str[128];
    sprintf(str, "Engine Number %d - Engine Type 0x%02X - Engine Name \"%s\" - Last Byte 0x%.2X",
        EngineNo, EngineType, EngineName, LastByte);
    DB->Debug_Print((String) str);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::I1xxButtonClick(TObject *Sender)
{
//
// Debug use only
//
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
if (Setup->Radio_COM_Mode && Setup->TXOnlyComm->Checked) {	// cannot do this function without full duplex communications
	DebugMessagePopup("Warning", MainForm->TXMsg, "");
    return;
    }
// Interrogate a single engine and display the results
    byte I1xxbyteResp[CMDMAX];
    byte byteResp[CMDMAX];  // Array for decoded binary response
    int iTIUCount;
    int respCode;			// integer for comm response code
    int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and TIU number in spinner
    int MyTIUNo = TIUSpin->ValueAsInt;
    int EngineType, LastByte = 0;
    char EngineName[17];
	char Command[10];
	(void) snprintf(Command, sizeof Command, "I%d", EngineNo + 1 + 128);	// Uses the DCS Engine Number in the "I1xx" command  (Operations Control Constructor)
	bool bretv = ptrTIUCommThread->Send_String_FIFO(EngineNo, Command, MyTIUNo, RemoteNo, 49, I_WAIT, NO_RECORD, NULL, &respCode, &iTIUCount, I1xxbyteResp);
    // Get the info about what switches the engine supports.
	if (bretv && iTIUCount >= 25) {    // Radio is 49 bytes, WiFi is around 36 bytes depending on the # characters in engine name
		if (Setup->Radio_COM_Mode->Checked) {     // in Radio mode, we get 49 bytes if an engine is found
            // Handle Radio COM
			// Got a good response
            // Response to I1xx command:
            // A656AA965A6A55555569555A55A566595999559A655556A556A6695A66666555665669656666656A666965555555
            //   A3  EB  1F  00  14  05  50  26  4C  45  20  52  53  2D  33  20  23  38  33  35  36  20  00
            //                     ^^^^ is is 0x00/0x10 for Steam, 0x05/0x85 for Diesel, and 0x25 Gas/Electric
            // see spreadsheet named "Command ('I1xx') Response.xls"  for decoding details
            EngineType = I1xxbyteResp[5];
            strncpy(EngineName, &I1xxbyteResp[6], 16);  // will not NULL terminate
            EngineName[16] = '\0';
            LastByte = I1xxbyteResp[22];
            // No format change necessary
            memcpy(byteResp, I1xxbyteResp, CMDMAX);
            // byteResp[] has the decoded response string
            }

		if (Setup->WTIU_WiFi_Mode->Checked) {    // in WTIU mode, we don't get an exact number of bytes because blanks at end of engine name are suppressed
			// The string starts with "I1xx"
            // Handle WTIU ASCII Response  I145:85;P&LE RS-3 #8356 ;01 okay
            // Got a good response
            int MyEngineNo;
            int retv = sscanf(I1xxbyteResp, "I%d:%x;%[^;];%x okay", &MyEngineNo, &EngineType, EngineName, &LastByte);
            assert(retv == 4);
            assert(EngineNo + 1 + 128 == MyEngineNo);
            }
       }
    /* Format() did not handle the %02X by inserting leading zeros. sprintf() did. Failed in
        both BCB4.0 and CB10.1
        The two formats: %02X and %.2X should produce the same result but with Format() they did not
        produce the leading zero.
    DB->Debug_Print(Format((String)"Engine Number %d - Engine Type 0x%02X - Engine Name \"%s\" - Last Byte 0x%.2X",
        ARRAYOFCONST((EngineNo, EngineType, EngineName, LastByte))), clBlue);
        */
    char str[128];
    sprintf(str, "Engine Number %d - Engine Type 0x%02X - Engine Name \"%s\" - Last Byte 0x%.2X",
        EngineNo, EngineType, EngineName, LastByte);
    DB->Debug_Print((String) str);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::SaveCRCClick(TObject *Sender)
{
WritetoDiskClick(WriteCRC);
}
//---------------------------------------------------------------------------

unsigned int __fastcall TMainForm::EncodedStringToDecimalInt(String Cmd, int Index, int Len)
{
// decode the encoded string into Binary
// Index starts at 1
unsigned int iVal = 0;
//  Decode
for (int idx = Index ; idx < (Index + Len); idx += 4) {
   	if ((Index + Len ) - idx < 3) continue; // don't decode unless we have 4 characters
	String sVal = bintodec8(bitswapRX(Convert16B8BNumber(Cmd.SubString(idx, 4))));
    iVal = (iVal << 8 ) | (sVal.ToInt() & 0xFF);
    }
return iVal;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::DecodeLast_ResponseButtonClick(TObject *Sender)
{
Last_Response_Decoded->Text = sDecodeString(Last_Response->Text, false);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::VarModeButtonClick(TObject *Sender)
{
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
const TButton * ButtonUsed = (TButton *)Sender;
assert(ButtonUsed);
// open Var control based on button pushed
if (ButtonUsed->Name.Pos("1")) {
	StartUpVarControl(1);
    }
if (ButtonUsed->Name.Pos("2")) {
	StartUpVarControl(2);
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::StartUpVarControl(int Chan)
{
// This function uses the TIU Spinner
// https://stackoverflow.com/questions/31649315/c-builder-button-click-function-reentry
LOCKEM()

ChannelNo = Chan;	// global location of Channel Number
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
const TCursor OldCursor = Screen->Cursor; 	// save cursor
Screen->Cursor = crHourGlass;
//
// Manually create form and show it
assert(TIUSpin->ValueAsInt >= 1 && TIUSpin->ValueAsInt <= TIUMAX);  // use TIU Number in spinner
assert(ChannelNo >= 1 && ChannelNo <= CONVMAX);
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner
// Many Variable Channel Control Forms
if (ptrConvMode[TIUSpin->ValueAsInt-1][ChannelNo-1] == NULL) {    // create the Form and show it - TIU 1-5 - 	Var ChannelNo 1-2
    // Note: EngineNo is not used in the command and could possibily set to FACTORYRESET
	ptrConvMode[TIUSpin->ValueAsInt-1][ChannelNo-1] = new TConvMode(this, RemoteNo, EngineNo, TIUSpin->ValueAsInt, ChannelNo);
																  // Pass "this" as the owner
	assert(ptrConvMode[TIUSpin->ValueAsInt-1][ChannelNo-1] != NULL);		// make sure that construction worked
    ptrConvMode[TIUSpin->ValueAsInt-1][ChannelNo-1]->ParentHandle = Handle; // for return messages
    // setup defaults on Operations form after startup
	} else {    // it's already created
	ptrConvMode[TIUSpin->ValueAsInt-1][ChannelNo-1]->Show();
	}
Screen->Cursor = OldCursor;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::DCSSetupClick(TObject *Sender)
{
// This function uses the TIU Spinner
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
// DCS Setup Form should be manually created as it is rarely needed
assert(TIUSpin->ValueAsInt >= 1 && TIUSpin->ValueAsInt <= TIUMAX);
// if selected TIU is not connected fail
if (!TIUSelection[TIUSpin->ValueAsInt - 1]) {
	DebugMessagePopup("Warning", MainForm->TIUActiveMsg, "");
	return;
    }
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
// One DCS Setup Form per TIU (5 total)
if (ptrDCS[TIUSpin->ValueAsInt-1] == NULL) {	// TIU 1-5
// Note: EngineNo is not used in the command and could possibily set to FACTORYRESET
	ptrDCS[TIUSpin->ValueAsInt-1] = new TDCS_Setup(this, RemoteNo, EngineNo, TIUSpin->ValueAsInt);     // Pass "this" as the owner
	assert(ptrDCS[TIUSpin->ValueAsInt-1] != NULL);	// make sure that construction worked
    ptrDCS[TIUSpin->ValueAsInt-1]->ParentHandle = Handle; // for return messages
	} else {   // it's already created
	ptrDCS[TIUSpin->ValueAsInt-1]->Show();
	}
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::LashUpButtonClick(TObject *Sender)
{
assert(Setup != NULL);		// setup must be created
// LashUp Form is automatically created so that the LashUp List is in memory
LashUp->Show();
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CMLashUpStartUp(TMessage &Message)
{
// process Start Lashup message
//
unsigned char LashUpEngineList[LASHUP_ENGINE_LIST_LENGTH];	// Bytes containing the LashUp in the form 0x2C 0x01 0x82 0x03 0xFF
(void) strncpy((char *)LashUpEngineList, (char *) Message.WParam, sizeof LashUpEngineList); 	// binary engine list from Word parameter

const int LUNo = Message.LParam & 0x00FF;	// set Lashup Number from Long Parameter -  101-120
if (LUNo == 0) return;						// no LashUp Number
assert(LUNo >= LASHUPMIN && LUNo <= LASHUPMAX);	// value 101 to 120 - DCS Engine Number always 102 (0x66)
const int TIU = (Message.LParam & 0xFF00) >> 8;	// set TIU Number from Long Parameter - TIU Number 1-5
assert(TIU >= 1 && TIU <= TIUMAX);
//
String LashUpName;
// LashUp name is put into LashUpData->Cells by LashUp_Selection.cpp
LashUpName = LashUp->LashUpData->Cells[LASHUP_NAME][LUNo - 100];            // Name of the LashUp
if (ptrLUNameList[LUNo]) { 	// LashUp name is put here by Program Control routine LashUpStartUp
	LashUpName = ptrLUNameList[LUNo];   // so it will override the LashUp name contained in LashUpData
    }

if (LashUpName == "*") return;				// no LashUp name
if (LashUpEngineList[0] == '\0') return;	// no LashUpEngineList

const TCursor OldCursor = Screen->Cursor; 	// save cursor
Screen->Cursor = crHourGlass;

// Manually create the Operation Form and show it
//
// Many Operation Control Forms for LashUps
if (ptrOp[LUNo] == NULL) {
    //  ---------------------------------------------------------------------
    // New Location of "U" command
	// Build Hex lash up string 0x2C 0x00 0x00 0x00 0xFF
	// create and setup the operations window
	// Create lashup "U" command followed by lash up string  0A 03 84 FF
	// 5555669955AA5A5556569559AAAA996556569696A9694D
	//   00  66  55  0A  03  84  FF  98  03  C3  BC		DCS v5, DCS v6 is different, see below
	//          'U'      ^^  ^^ binary
    //  ---------------------------------------------------------------------
    int respCode = UNDETERMINED;
	bool retv = ptrTIUCommThread->Send_String_FIFO(LUNo, "U", TIU, RemoteNo, 11, VERY_LONG_WAIT, RECORD_OK, LashUpEngineList + 1, &respCode);
		// uses LashUpEngineList but skiping over the 0x2C, starts right at the head engine number
		// LashUp is always sent to the TIU as engine number 101 (DCS EngineNo #102 0x66).
        // It takes quite a while for the TIU to setup the LashUp (almost 2 seconds)
        // NOTE NOTE : this command is handled as a special case inside of ptrTIUCommThread->Send_String_FIFO()
        // and Playback - "U"
	//  ---------------------------------------------------------------------
    // I changed the next line as a test - it seems to be working well
	//if (!retv || (respCode == 0x00)) {  // respCode of 0x00 is actually a failure as the L.O. nibble must be the TIU number
	if (!retv || (respCode >= UNDETERMINED)) {  // respCode of 0x00 is actually a failure as the L.O. nibble must be the TIU number
    	// U command failed
	    Screen->Cursor = OldCursor;		// restore cursor
		//new TMessageDisplayThread(false, this, Handle, "Error", "LashUp " + String(LUNo) + " not on track (LashUp Start Up)", "", UIPOPUPTIMEOUT);
		//DebugMessagePopup( "Error", "LashUp " + String(LUNo) + " not on track (LashUp Start Up)");
		new TMessageDisplayThread(false, this, Handle, "Warning", ((LUNo >= 101) ? "Lashup " : "Engine ") + String(LUNo >= 101 ? LUNo - 100 : LUNo) + " not on track (LashUp Start Up)", "", UIPOPUPTIMEOUT);
		return;	// startup failed - don't show Op window
        }
	//  ---------------------------------------------------------------------
	/*  DCS V5 Commands look like this: -------------------------------------------

    In version 5 and earlier, the lash up engine list was sent as
    binary numbers, preceeded by a comma "," 0x2C and terminated
    by 0xFF. Here is an example of DCS engines 15 and 16 (Remote engines 14 and 15)
    with the second engine running in reverse.

	startup - "u4," command followed by lashup string  0A 03 84 FF
	5555669965AA656969595A5556569559AAAA99665656A9969A9A4D
	  00  66  75  34  2C  0A  03  84  FF  99  03  E9  CF
	         'u' '4' ','  ^^  ^^  ^^  binary DCS engine numbers
	whistle is command "w2," followed by lash up string
	5555669966AA666569595A5556569559AAAA9A655656965AA9A94D
	  00  66  77  32  2C  0A  03  84  FF  9A  03  87  FC
	         'w' '2' ','
	555566996695569956995699559969595A5556569559AAAA9A66565656A699AA4D
	  00  66  62  46  46  46  44  2C  0A  03  84  FF  9B  03  53  DD
	         'b' 'F' 'F' 'F' 'D' ','
	--------------------------------------------------------------------- */
    /*  DCS V6 Commands look like this: ------------------------------------------

    The way the Lash Up engine list is sent to the TIU was changed dramatically
    in DCS v6.

    In V6, the lash up engine list is sent as ASCII characters but
    still preceeded by a comma "," 0x2C and terminated by 0xFF. The ASCII is
    the hex representation of the engine number as appeared in v5 commands.
    Here is an example of DCS engines 15 and 16 (Remote engines 14 and 15):

    --V6
	5555669965AA656969596565569965666565AAAA956955596AA9999A4D
	  00  66  75  34  2C  30  46  31  30  FF  94  04  7E  CD
	         'u' '4' ',' '0' 'F' '1' '0'				V6 uses 2 digit ASCII DCS Engine number

    In both cases, reverse running is indicated by adding 0x80 (decimal 128) to the
    engine number before the command is created (H.O. bit is turned on).
    --------------------------------------------------------------------- */
    //
    // RTC no longer supports V5 or earlier

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

	ptrLUEngineList[LUNo] = new char [LASHUP_ENGINE_LIST_LENGTH];
	assert(ptrLUEngineList[LUNo] != NULL);		// make sure that memory allocation worked
    strncpy(ptrLUEngineList[LUNo], LashUpEngineList, LASHUP_ENGINE_LIST_LENGTH);
    //
    int HeadEngine;
    // New use of GetLashUpEngine tested ok
    HeadEngine = MainForm->GetLashUpEngine(LashUpEngineList, 0);  // get head engine

    // apparently the WTIU requires a "yX" command after the 'U' command
    AnsiString aCommand = "y" + String(HeadEngine + 1);
	(void)ptrTIUCommThread->Send_String_FIFO(2, aCommand.c_str(), TIU, RemoteNo, 11, LONG_WAIT, RECORD_OK);

    //  ---------------------------------------------------------------------
	ptrOp[LUNo] = new TOp(this, RemoteNo, LUNo, TIU, LashUpName, ptrLUEngineList[LUNo]);   // create the Form and show it
											  // Pass "this" as the owner
                                              // LashUpNo 101-120 (always DCS Number #102 0x66)
	assert(ptrOp[LUNo] != NULL);		// make sure that construction worked
    ptrOp[LUNo]->ParentHandle = Handle; // for return messages - back to MainForm

	// Startup
	int iAcc = 0, iDec = 0; // routine returns default values if we cannot communicate.
	if (!Setup->StandardAccDec->Checked) {  // if "Standard Acc/Dec" is not checked
		// Use the Acc/Dec rates from the lead engine for all of the other engines
		// Read Acc/Dec rates from head engine and display them, and send them to the LashUp
		// GetAccDecValues() returns default values if we cannot communicate.
		// Note: LashUpEngineList[] already contains DCS engine numbers so we must return it
		// to an engine number because GetAccDecValues calls ptrTIUCommThread->Send_String_FIFO which increments it
		(void) GetAccDecValues(HeadEngine, TIU, &iAcc, &iDec);
		// TODO : - Consider saving the Acc/Dec rates for the Middle and Tail Engines in the LashUp and restoring them on shutdown
	    }
	// Volume setting matches the head engine
    // Setup volume control defaults before startup occurs
    //
    try {
      // Set Engine sound volumes.
	  // Needed because Horn & Bell Sounds are muted in a LashUp and I don't seem to restore them correctly
      //
      char str[15];
      snprintf(str, sizeof str, "v0%03d", EngineData->Cells[VOL_MASTER][HeadEngine].ToInt()); 	// Master Volume
      (void) ptrTIUCommThread->Send_String_FIFO(LUNo, str, TIU, RemoteNo, 11, SHORT_WAIT, RECORD_OK, LashUpEngineList);
      //ptrOp[LUNo]->LastSentMasterVolumeValue = EngineData->Cells[VOL_MASTER][HeadEngine].ToInt();

      snprintf(str, sizeof str, "v1%03d", EngineData->Cells[VOL_ENGINE][HeadEngine].ToInt()); 	// Engine Sounds
      (void) ptrTIUCommThread->Send_String_FIFO(LUNo, str, TIU, RemoteNo, 11, SHORT_WAIT, RECORD_OK, LashUpEngineList);

      snprintf(str, sizeof str, "v2%03d", EngineData->Cells[VOL_ACCENT][HeadEngine].ToInt()); 	// Accent Sounds
      (void) ptrTIUCommThread->Send_String_FIFO(LUNo, str, TIU, RemoteNo, 11, SHORT_WAIT, RECORD_OK, LashUpEngineList);

      snprintf(str, sizeof str, "v3%03d", EngineData->Cells[VOL_HORN][HeadEngine].ToInt()); 	// Horn Sounds
      (void) ptrTIUCommThread->Send_String_FIFO(LUNo, str, TIU, RemoteNo, 11, SHORT_WAIT, RECORD_OK, LashUpEngineList);

      snprintf(str, sizeof str, "v4%03d", EngineData->Cells[VOL_BELL][HeadEngine].ToInt());	// Bell Sounds
      (void) ptrTIUCommThread->Send_String_FIFO(LUNo, str, TIU, RemoteNo, 11, SHORT_WAIT, RECORD_OK, LashUpEngineList);
      }
    catch ( EConvertError & oops ){
      // first time this Engine is used, the EngineData StringGrid is empty and the ToInt() function
      // returns an EConvertError exception
      DebugMessagePopup("Warning", "Empty Engine Data Field in Lashup Startup- Repaired", "");
      CaughtErrors->Value ++;
      }
    catch (...) {
      CaughtErrors->Value ++;
      DebugMessagePopup("Error", "Unknown Startup Problem in Lashup Volume Settings", "");
      }
    //
	// Command "u4" for normal, "u6" for Extended
	// if either SHIFT key is pressed whilst clicking "Startup" button, use the extended startup sound
	//
	if (Setup->ExtendedSUSD->Checked || (ShiftKeyState < 0)) {
		(void)ptrTIUCommThread->Send_String_FIFO(LUNo, COMMAND_STARTUP_EXTENDED, TIU, RemoteNo, 11, LONG_WAIT, RECORD_OK, LashUpEngineList);    // in CMLashUpStartUp()
	    } else {
		(void)ptrTIUCommThread->Send_String_FIFO(LUNo, COMMAND_STARTUP, TIU, RemoteNo, 11, LONG_WAIT, RECORD_OK, LashUpEngineList);    // in CMLashUpStartUp()
		}
    //---------------------------------------------------------------------------
    // TODO : "d0" and "s0" often fail right after startup when first powered on - I added a short delay
    //---------------------------------------------------------------------------
#if USED0ANDS0AFTERSTARTUP
    Delay(WAITAFTERSTARTUP);
	// set DIR forward  "d0"
	(void)ptrTIUCommThread->Send_String_FIFO(LUNo, DIRECTION_FORWARD, TIU, RemoteNo, 11, SHORT_WAIT, RECORD_OK, LashUpEngineList);
	// set speed to zero unless it already is
	if (ptrOp[LUNo]->SpeedSlider->Value >= 0.01) {
	    // BUG workaround - slider starts out at 10 	// causes the 's0' command to be sent because the slider is at 10
		// This bug is now taken care of in the Op_Control.cpp file
		ptrOp[LUNo]->SetSpeedSliderValue(0.0);
	    }
#else
    Delay(WAITAFTERSTARTUP);
#endif
    // setup the Op Window
    // Engine type matches the head engine
    // MainForm->GetLashUpEngine(LashUpEngineListOp, 0);  // is the DCS number of the head engine
    // Note: LashUpEngineList[] contains DCS engine numbers so we have to subtract one
    // Note: in lashup mode, many of the settings in the engines are controlled automatically by the TIU
    // based on the sequence of engines inthe engine list (ie, head - middle - tail).
    // These settings include : bell, headlight, coupler operation and whistle (and maybe more).
    int HeadEngineType = EngineData->Cells[ENG_TYPE][HeadEngine].ToInt();
    SetComboBox(ptrOp[LUNo]->EngineTypeComboBox, HeadEngineType);
	// 5 LED switches
    // ENG_HDLT contains five encoded values - Headlight State, Rule 17, Bell on with horn, Auto Bell on Startup and
    // Auto Whistle/Horn on Startup
    // In a lashup, Headlight state is left to TIU control
    //SetSwitch(ptrOp[LUNo]->HDLTSwitch, EngineData->Cells[ENG_HDLT][HeadEngine].ToIntDef(0) & 0x01);
    SetLED(ptrOp[LUNo]->SimulatedRule17LED, (EngineData->Cells[ENG_HDLT][HeadEngine].ToIntDef(0) >> 1) & 0x01);
    SetLED(ptrOp[LUNo]->BellOnWithHornLED, (EngineData->Cells[ENG_HDLT][HeadEngine].ToIntDef(0) >> 2) & 0x01);
    SetLED(ptrOp[LUNo]->AutoBellLED, (EngineData->Cells[ENG_HDLT][HeadEngine].ToIntDef(0) >> 3) & 0x01);
    SetLED(ptrOp[LUNo]->AutoWhistleLED, (EngineData->Cells[ENG_HDLT][HeadEngine].ToIntDef(0) >> 4) & 0x01);
	// Smoke setting matches the head engine - off first then setting
    SetSwitch(ptrOp[LUNo]->SmokeSwitch, true);
    SetSwitch(ptrOp[LUNo]->SmokeSwitch, false);	// start with smoke/exhaust off
    // set set to saved value unless Smoke/Exhaust is forced OFF at startup
    if (!Setup->SmokeOFFatStartup->Checked) SetSwitch(ptrOp[LUNo]->SmokeSwitch, EngineData->Cells[ENG_SMOKE][HeadEngine].ToIntDef(0));
    // RAM 0x24 bit 3
    SetSwitch(ptrOp[LUNo]->ClickClackSwitch, EngineData->Cells[ENG_FCC][HeadEngine].ToIntDef(0));
    // ENG_SMOKELEVEL contains two encoded values - SmokeGroup (2 bits) and AutoLaboredSmokeSwitch (1 bit)
    // RAM 0x24 bits 1,0
    SetRadioGroup(ptrOp[LUNo]->SmokeGroup, EngineData->Cells[ENG_SMOKELEVEL][HeadEngine].ToIntDef(0) & 0x03);
    SetLED(ptrOp[LUNo]->AutoLaboredSmokeLED, (EngineData->Cells[ENG_SMOKELEVEL][HeadEngine].ToIntDef(0) >> 2) & 0x01);
    //
    // RAM 0x24 bit 2
    SetSwitch(ptrOp[LUNo]->CabChatSwitch, EngineData->Cells[ENG_CHAT][HeadEngine].ToIntDef(0));
    // RAM 0x24 bit 6
    SetSwitch(ptrOp[LUNo]->ProtoChuffSwitch, EngineData->Cells[ENG_PROTOCHUFF][HeadEngine].ToIntDef(0));
    // the 'g' command does different things for diesels than for steam engines - handled inside of ChuffRateSwitchChanged()
    // ie - ChuffRateSwitch() does nothing if this is a diesel engine.
    SetSwitch(ptrOp[LUNo]->ChuffRateSwitch, EngineData->Cells[ENG_CHUFFRATE][HeadEngine].ToIntDef(0));
    // RAM 0x24 bit 4
    SetSwitch(ptrOp[LUNo]->BrakesSwitch, EngineData->Cells[ENG_BRAKES][HeadEngine].ToIntDef(0));

    // For max speed, the the lowest max of the engines in the LashUp
    // (LashUpEngineList, 2nd byte is DCS engine number so need to subtract one)
    int mx = Setup->MaxSpeed->Value; 	// start at overall max speed
    // New use of GetLashUpEngine tested ok
    for (int Spot = 0; Spot < MAXLASHUP ; Spot++) {
        // V5 and earlier
        //int idx = (Spot * (Setup->WTIU_WiFi_Mode->Checked? 2 : 1)) + 1;
        // V6 and later
        int idx = (Spot * 2) + 1;
    	if ((LashUpEngineList[idx] & 0x7F) == 0x7F) break;    // 0xFF indicates end of list
        int LashupEngineNo = MainForm->GetLashUpEngine(LashUpEngineList, Spot);  // get engine number
        //  ---------------------------------------------------------------------
        int nextmx = EngineData->Cells[ENG_MAXSPEED][LashupEngineNo].ToIntDef(60);
        if (nextmx < mx) mx = nextmx;
    	}

    if (mx < 10) mx = Setup->MaxSpeed->Value; 	// maximum value
    ptrOp[LUNo]->MaxSpeedSpin->Value = mx;
    //ptrOp[LUNo]->MaxSpeedSpin->MaxValue = mx; // bug found by Carl Ross - replace with next line
    ptrOp[LUNo]->MaxSpeedSpin->MaxValue = Setup->MaxSpeed->Value;
	ptrOp[LUNo]->SpeedSlider->SignalSettings->DigitalTo = ptrOp[LUNo]->MaxSpeedSpin->Value;
	ptrOp[LUNo]->SpeedSlider->SignalSettings->ValueTo = ptrOp[LUNo]->MaxSpeedSpin->Value;
	ptrOp[LUNo]->SpeedSlider->ScaleSettings->Steps = ptrOp[LUNo]->MaxSpeedSpin->Value / 10;
	if (Setup->StandardAccDec->Checked) {
		// set Acc and Dec values to 1
        ptrOp[LUNo]->SetStandardAccDecValues(HeadEngine, TIU); // on Lashup Startup from the head engine
	    } else {
	    // Load Acc and Dec values as read from the first engine into the LashUp
	    SetNumSpin(ptrOp[LUNo]->AccSpin, iAcc);		// Sends Acc Rate to the LashUp
	    SetNumSpin(ptrOp[LUNo]->DecSpin, iDec);		// Sends Dec Rate to the LashUp
        }
    StartedConsists->Value++;   // in CMLashUpStartUp()
	} else {    // it's already created
	ptrOp[LUNo]->Show();
	}

// if either CTRL key is pressed whilst clicking "Startup" button, show the Sound form also
if (CtrlKeyState < 0) {
	// Send CM_LASHUPSOUNDCTRLS message to MainForm which will open the Volume Control Form
	// for the message, use the same message that was sent to this routine
	(void) PostMessage(Handle, CM_LASHUPSOUNDCTRLS, Message.WParam, Message.LParam);
    }
AltKeyState = ShiftKeyState = CtrlKeyState = 0;
Screen->Cursor = OldCursor;
return;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CMLashUpSoundCtrls(TMessage &Message)
{
//
// process Lashup Volume Control message for LashUps 101-120
//
char LashUpEngineList[LASHUP_ENGINE_LIST_LENGTH];			// Bytes containing the LashUp
(void) strncpy((char *)LashUpEngineList, (char *) Message.WParam, sizeof LashUpEngineList); 	// binary engine list from Word parameter

const int LUNo = Message.LParam & 0x00FF;	// set Lashup Number from Long Parameter
if (LUNo == 0) return;				// no LashUp

assert(LUNo >= LASHUPMIN && LUNo <= LASHUPMAX);	// value 101 to 120 - DCS Engine Number always 102 (0x66)
int TIU = (Message.LParam & 0xFF00) >> 8;		// set TIU Number from Long Parameter
assert(TIU >= 1 && TIU <= TIUMAX); 	// TIU Number is 1 to 5
assert (ptrOp[LUNo] != NULL);	// Lashup must be running

String LashUpName;
// LashUp name is put into LashUpData->Cells by LashUp_Selection.cpp
LashUpName = LashUp->LashUpData->Cells[LASHUP_NAME][LUNo - 100];            // Name of the LashUp

if (ptrLUNameList[LUNo]) { 	// LashUp name is put here by Program Control routine LashUpStartUp
	LashUpName = ptrLUNameList[LUNo];   // so it will override the LashUpName contained in LashUpData
    }

if (LashUpName == "*") return;				// no LashUp name
if (LashUpEngineList[0] == '\0') return;	// no LashUpEngineList
assert(ptrOp[LUNo] != NULL);	// engine must be running

// Manually create form and show it
// Many Volume Control Forms
if (ptrVolume[LUNo] == NULL) {
	ptrVolume[LUNo] = new TVolume(this, RemoteNo, LUNo, TIU, LashUpName, ptrLUEngineList[LUNo]);     // Pass "this" as the owner
	assert(ptrVolume[LUNo] != NULL);		// make sure that construction worked
	ptrVolume[LUNo]->ParentHandle = Handle; // for return messages - back to MainForm
	// TODO : - Consider using the volume setting for the head engine as the volume for the consist
	} else {   // it's already created
	ptrVolume[LUNo]->Show();
	}
}
//---------------------------------------------------------------------------

bool __fastcall TMainForm::IsConsistRunning(int Consist)    // indexed by actual engine number
{
assert((Consist >= (Setup->AllowZero->Checked ? 0 : 1)) && (Consist <= LASHUPMAX));	// value 0 to 120 - Any engine or LashUp
if (ptrOp[Consist] != NULL) return true;	// if a pointer to the Op Window is not NULL, then the consist is running
return false;	// not running
}
//---------------------------------------------------------------------------

bool __fastcall TMainForm::IsLashUpRunning(int LUNo)    // LUNo is actual Lashup Number
{
assert(LUNo >= LASHUPMIN && LUNo <= LASHUPMAX);	// value 101 to 120 - Any LashUp
if (ptrLUEngineList[LUNo] != NULL) return true;	// if a pointer to the LashUpEngineList is not NULL, then the LashUp is running
return false;	// not running
}
//---------------------------------------------------------------------------

bool __fastcall TMainForm::IsTIUConnected()
{
// returns true if the TIU has responded to commands
return bTIUConnected;
}
//---------------------------------------------------------------------------

bool __fastcall TMainForm::IsRadioCOMConnected()
{
// returns true if the Radio serial port has opened successfully
return bRadioCOMConnected;
}
//---------------------------------------------------------------------------

bool __fastcall TMainForm::IsRFIDCOMConnected()
{
// returns true if the RFID serial port has opened successfully
return bRFIDCOMConnected;
}
//---------------------------------------------------------------------------

bool __fastcall TMainForm::IsWTIUWiFiConnected()
{
// returns true if the WTIU WiFi port has opened successfully
return bWTIUWiFiConnected;
}
//---------------------------------------------------------------------------

bool __fastcall TMainForm::IsRFIDWiFiConnected()
{
// returns true if the RFID serial port has opened successfully
return bRFIDUDPWiFiConnected;
}
//---------------------------------------------------------------------------

bool __fastcall TMainForm::IsRadioConnected()
{
// returns true if the Radio (Serial or WTIU WiFi) has opened successfully
return bRadioCOMConnected || bWTIUWiFiConnected;
}
//---------------------------------------------------------------------------

bool __fastcall TMainForm::IsRFIDConnected()
{
// returns true if the RFID (Serial or WiFi) has opened successfully
return bRFIDCOMConnected || bRFIDUDPWiFiConnected || bRFIDTCPWiFiConnected;
}
//---------------------------------------------------------------------------

bool __fastcall TMainForm::IsEngineActive(int iEngineNo)
{
// returns true if the engine is active
if ((EngineData->Cells[ENG_NAME][iEngineNo] != "*") && (EngineData->Cells[ENG_STATE][iEngineNo] == ACTIVE)) return true;
return false;
}
//---------------------------------------------------------------------------

bool __fastcall TMainForm::DoesEngineExist(int iEngineNo)
{
// returns true if the engine exists
if (EngineData->Cells[ENG_NAME][iEngineNo] != "*") return true;
return false;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::InfoButtonClick(TObject *Sender)
{
// Splash Window Information Test
Splash = new TSplash(Application);
Splash->Memo1->Lines->Strings[0] = "Remote Train Control - " + String(Version);
Splash->Show();			// Show Splash Window
Splash->Update();		// Paint Splash Window
iSS = 0;					// Turn off future Splash Window
AboutRTC->Checked = true;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::LashUpShow(int LashUp)
{
assert (LashUp >= LASHUPMIN && LashUp <= LASHUPMAX);
if (ptrOp[LashUp] == NULL) return;	//engine not running
ptrOp[LashUp]->Show();
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::SetEngineintoCommandModeClick(TObject *Sender)
{
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
    return;
    }
(void)ptrTIUCommThread->Send_String_FIFO(EngineNo, "m4", MyTIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK);
Delay(m4DELAY);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::SetEngineintoLegacyModeClick(TObject *Sender)
{
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
    return;
    }
(void)ptrTIUCommThread->Send_String_FIFO(EngineNo, "m8", MyTIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::SetEngineintoConventionalModeClick(
      TObject *Sender)
{
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
    return;
    }
(void)ptrTIUCommThread->Send_String_FIFO(EngineNo, "m2", MyTIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::TMCCButtonClick(TObject *Sender)
{
// This code should work with auto create on OR off.
// ------------ With programmed create using C++ Builder's generated pointer
// This is test code only - final implementation should be using a selection form just like for LashUps

// This function uses the TIU Spinner
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
//
const TCursor OldCursor = Screen->Cursor; 	// save cursor
Screen->Cursor = crHourGlass;
//
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner
int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
    return;
    }
//
if (!TMCCOp) {
	TMCCOp = new TTMCCOp(this, RemoteNo, EngineNo, MyTIUNo, EngineData->Cells[ENG_NAME] [EngineNo], NULL);     // Pass "this" as the owner
	assert(TMCCOp != NULL);   		// make sure that construction worked
    TMCCOp->ParentHandle = Handle;  // for return messages
    // TODO : need TMCC engine number stored in EngineData and passed to TMCCOp.
    (void)ptrTIUCommThread->Send_String_FIFO(EngineNo, "T0D", MyTIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK);
	// TODO : - Send saved settings to TMCC Engine
   	}
TMCCOp->Show();	// since I used the C++ Builder's generated pointer, it will get automatically
				// deleted at close if I don't do it first (in CMTMCCShutdown())
// TODO : TMCC operation is not tested and not complete
// Need to add engine selection screen
// Need to add ptrTMCCOp[] array and its support
//
Screen->Cursor = OldCursor;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CMTMCCShutdown(TMessage &Message)
{
// TODO : TMCC operation is not fully coded
// process TMCC Engine Shutdown message - Engines 1-99
const int TIU = Message.WParam;		// set TIU Number from Word Parameter
const int Consist = Message.LParam;	// set Consist Number from Long Parameter
assert (TIU >= 1 && TIU <= TIUMAX);
assert (Consist >= 1 && Consist <= ENGINEMAX);	// 1-99 TMCC Engines
assert (TMCCOp != NULL);	// not running - caller must validate that the consist is running
(void)ptrTIUCommThread->Send_String_FIFO(Consist, "T50", TIU, RemoteNo, 11, SHORT_WAIT, RECORD_OK);
(void)ptrTIUCommThread->Send_String_FIFO(Consist, "T0F", TIU, RemoteNo, 11, SHORT_WAIT, RECORD_OK);
// Close the form
delete TMCCOp;
TMCCOp = NULL;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::IdleTimerTimer(TObject *Sender)
{
/*
// Disconnect after 10 seconds of idle if we are connected
if ((Setup->DropDTRonIdle->Checked || Setup->DropRTSonIdle->Checked) && IsTIUConnected()) {
	IdleTimer->Tag++;
    if (IdleTimer->Tag >= 10) {
        if (Setup->DropDTRonIdle->Checked && (!TIUEMULATORMODE && !RADIOMODE)) DTRSwitch->Checked = false;
        if (Setup->DropRTSonIdle->Checked) RTSSwitch->Checked = false;
        //ConnectedRadioLED->Flashing = true;
        IdleTimer->Tag = 0;		// restart Timer Counter
        }
    }
// Idle timer keeps on running
*/
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CMTIUChar(TMessage &Message)
{
//
// Display the encoded TIU character received in the TIUBuffer - CM_TIUCHAR
// NOTE: Used only for USB serial communications
//
const unsigned char Char1 = Message.WParam & 0x00FF;		// set received Char from Word Parameter
const int TIUbufCount = Message.LParam;			// set byte count from Long Parameter
// put the character in the buffer - no filtering is done
// convert Int to Hex  -----------------------------------------------
const String strHex = IntToHex(Char1 & 0xFF, 2);
// if the previous text was not red, output a CRLF and set text to Red
if (TIULog->Checked) {
    if (DEBUGGING) {
        if (TIUBuffer->SelAttributes->Color != clRed) {
            TIUBuffer->SelAttributes->Color = clRed;
            TIUBuffer->SelStart = TIUBuffer->GetTextLen();
            //TIUBuffer->SelText = TEXT("\r\n");
            //TIULineCount->Caption = String(TIULineCount->Caption.ToInt() + 1);
            ScrollEnd(TIUBuffer);    	// remain at end of TIU Buffer
            }
        // put the character into the buffer;
        TIUBuffer->SelText = strHex;
        //
        if ((Char1 == 0x4D)) {  // CR LF on 0x4D EOT character
            TIUBuffer->SelStart = TIUBuffer->GetTextLen();
            TIUBuffer->SelText = TEXT("\r\n");
            TIULineCount->Caption = String(TIULineCount->Caption.ToInt() + 1);
            ScrollEnd(TIUBuffer);    	// remain at end of TIU Buffer
            }
        }
    }
//
if (DEBUGGINGX(4)) {
    MainForm->TIU_Char_Count->Text = TIUbufCount;  // show the new count
    MainForm->TIU_Total_Byte_Count->Value += 1;
    }
//
// TODO : is this next line needed?
if (RFIDLog->Checked && Setup->Duplex->Checked && (Setup->RFID_COM_Mode->Checked || Setup->RFID_WiFi_Mode->Checked) ) {
	// also output to RFID Buffer
    // if the previous text was not red, output a CRLF and set text to Red
    if (DEBUGGING && RFIDBuffer->SelAttributes->Color != clRed) {
    	RFIDBuffer->SelAttributes->Color = clRed;
        RFIDBuffer->SelStart = RFIDBuffer->GetTextLen();
        RFIDBuffer->SelText = TEXT("\r\n");
        ScrollEnd(RFIDBuffer);    	// remain at end of Buffer
	    }
    // Hex display character in RFID Buffer
	RFIDBuffer->SelText = strHex;
    if (DEBUGGING && (Char1 == 0x4D)) {  // CR LF on 0x4D EOT character
        RFIDBuffer->SelStart = RFIDBuffer->GetTextLen();
    	RFIDBuffer->SelText = TEXT("\r\n");
        ScrollEnd(RFIDBuffer);    	// remain at end of Buffer
        }
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CMTIUASCIIChar(TMessage &Message)
{
//
// Display TIU ASCII character received in the TIUBuffer - CM_TIUASCIICHAR
// Handles one character at a time.
//
assert(false);  // this function is not used anymore
const char Char1 = Message.WParam & 0x00FF;		// set received Char from Word Parameter
const int TIUbufCount = Message.LParam;			// set byte count from Long Parameter
if (TIULog->Checked) {
    if (DEBUGGING) {
        // if the previous text was not red, output a CRLF
        // and set text to Red
        if (TIUBuffer->SelAttributes->Color != clRed) {
            TIUBuffer->SelStart = TIUBuffer->GetTextLen();
            TIUBuffer->SelText = TEXT("\r\n");
            TIULineCount->Caption = String(TIULineCount->Caption.ToInt() + 1);
            ScrollEnd(TIUBuffer);    	// remain at end of TIU Buffer
            TIUBuffer->SelAttributes->Color = clRed;
            }
        // put the character into the buffer;
        TIUBuffer->SelStart = TIUBuffer->GetTextLen();
        TIUBuffer->SelText = Char1;
        if (Char1 == '\n') {
        ScrollEnd(TIUBuffer);    	// remain at end of TIU Buffer
            }
        }
    }
if (DEBUGGINGX(4)) {
    MainForm->TIU_Char_Count->Text = TIUbufCount;   // show the new count
    MainForm->TIU_Total_Byte_Count->Value += 1;
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CMRFIDASCIIChar(TMessage &Message)
{
// process RFID ASCII Character Received message - CM_RFIDASCIICHAR
//
if (RFIDLog->Checked) {
    const char Char1 = Message.WParam & 0x00FF;		// set received Char from Word Parameter
    const int iRFIDbufCount = Message.LParam;		// set count from Long Parameter
    assert((Char1 >= '0' && Char1 <= 'F') || Char1 == '\n');
    // set text to Green
    RFIDBuffer->SelAttributes->Color = clGreen;
    // put the character into the buffer;
    RFIDBuffer->SelStart = RFIDBuffer->GetTextLen();
    RFIDBuffer->SelText = Char1;
    //
    if (Char1 == '\n') {
        RFID_Char_Count->Text = iRFIDbufCount;  		// show the count
        ScrollEnd(RFIDBuffer);    	// remain at end of Buffer;
        }
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::LoadEngineData (TStringGrid * ED)
{
// Fill the Engine Data Grid from a disk file
// read Engine Number from Registry if it exists
// sets bTIUDataLoaded to true

// Loaded once at the beginning of the run
// and saved once at the end of the run

assert(ED != NULL);
if(bEngineDataLoaded) return;	// if already loaded then do nothing
//
const String EngineDataFile = String(cStartDir)  + "\\" + "EngineData.ini";
//
// Fill the Engine Data Grid from a disk file
//  The Index parameter is the number of the row, where the value of the first row in the grid is zero.
//  The Index parameter is the number of the column, where the value of the first column in the grid is zero.

bEngineDataCorrupt = false;
bEngineDataLoaded = false;

__try {
    TStringList * ptrEngDat = new TStringList();	// deleted in MainForm->CloseAllWindows()
    assert(ptrEngDat != NULL);		// make sure that construction worked
    ptrEngDat->Sorted = false;
    ptrEngDat->Sorted = false;
    #if __BORLANDC__ >= 0x720
    ptrEngDat->Duplicates = System::Classes::dupIgnore;	// needed because of an ambiguity in Emabarcadero CB10.1
    #else
    ptrEngDat->Duplicates = dupIgnore;
    #endif
   	try {
    	if (FileExists(EngineDataFile)) {
   			// read it
			ptrEngDat->LoadFromFile(EngineDataFile);
   	    	// transfer data into TStringGrid from the TStringList
            ED->Rows[ENG_LABEL_ROW]->CommaText = ptrEngDat->Strings[ENG_LABEL_ROW];
			assert(ED->Rows[ENG_LABEL_ROW]->Count == ENGDAT_COUNT);
			// transfer data into TStringGrid from the TStringList
			for (int EngNum = ENG_TOPROW ; EngNum < ED->RowCount ; EngNum++) {
	    		ED->Rows[EngNum]->CommaText = ptrEngDat->Strings[EngNum];
                assert(ED->Rows[EngNum]->Count == ENGDAT_COUNT);
                // Validate Data
                const int field = ValidateEngineData(ED, EngNum);
				if (field != -1) throw((EngNum * 100) + field);
				}
			if (DEBUGGING) DB->Debug_Print("Engine Data Loaded from " + EngineDataFile);
   	    	} else {
           	// Engine Data Files does not exist so create an empty TStringGrid
   			// Titles and Engine Numbers- needed when the grid is first created
			ED->Rows[ENG_LABEL_ROW]->CommaText = ENGDAT_TITLE;
			ptrEngDat->Add(ED->Rows[ENG_LABEL_ROW]->CommaText);
			assert(ED->Rows[ENG_LABEL_ROW]->Count == ENGDAT_COUNT);
   			for (int EngNum = ENG_TOPROW; EngNum < ED->RowCount ; EngNum++) {
       			// Insert Engine Number and switch setting, Engine Name is "*"
				ED->Rows[EngNum]->CommaText = String(EngNum) + ENGDAT_INIT;
				ptrEngDat->Add(ED->Rows[ENG_LABEL_ROW]->CommaText);
                assert(ED->Rows[EngNum]->Count == ENGDAT_COUNT);
   				}
			ED->Cells [ENG_NAME][ENG_ALLROW] = "All Engines";
			if (DEBUGGING) DB->Debug_Print("Engine Data Created (Empty)");
			}
         if (ptrEngDat) {
            delete ptrEngDat;
            }
       	}
        catch (int field) {
       		DebugMessage("Saved Engine Data File Field (" + String(field) + ") is Corrupt : " + EngineDataFile);
       		bEngineDataCorrupt = true;	// loaded but corrupt
       		CaughtErrors->Value ++;
        	}
        catch ( ... ){
       		DebugMessage("Saved Engine Data File is Corrupt : " + EngineDataFile);
       		bEngineDataCorrupt = true;	// loaded but corrupt
       		CaughtErrors->Value ++;
       		}
    }
__finally {
	bEngineDataLoaded = true;
    }

// read Preferences from registry if they exist
TRegIniFile * const RegFile = new TRegIniFile(RegFileRootKey);
assert(RegFile != NULL);		// make sure that construction worked
int EngineNo;
__try {
	EngineNo = RegFile->ReadInteger("Options", "Engine", EngineSpin->ValueAsInt);
	RFIDudpRX->Port = RegFile->ReadString("WiFi", "UDP", RFIDudpRX->Port);
	RFIDtcpRX->Port = RegFile->ReadString("WiFi", "TCP", RFIDtcpRX->Port);
    }
__finally {
	// Deallocate RegFile
	delete RegFile;
    }
assert(EngineNo >= ENGINEMIN && EngineNo <= ALLENGINE);	// Engine Numbers only and All Engine Mode

EngineSpin->ValueAsInt = EngineNo;

ActiveEngineCount->ValueAsInt = 0;
InactiveEngineCount->ValueAsInt = 0;

// highlight the value in the StringGrid
ED->Col = ENG_NAME;
ED->Row = EngineNo;

// Update Active/Inactive Engine Counts
CountActiveEngines();

// Make sure the selected engine is visible in the middle row if possible
if (EngineNo < 3)
	ED->TopRow = 1;
    else
	ED->TopRow = EngineNo - 2;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::SaveEngineData (TStringGrid * ED)
{
// save all TIU specific data and sets bTIUDataLoaded to false
assert(ED != NULL);

// if the Engine Data is not loaded then do nothing.
if (!bEngineDataLoaded) return;

const String EngineDataFile = String(cStartDir)  + "\\" + "EngineData.ini";
if (bSaveRegistryInfo) {
	// write selected Engine Number and selected AIU Number to the Registry
	TRegIniFile * const RegFile = new TRegIniFile(RegFileRootKey);
	assert(RegFile != NULL);		// make sure that construction worked
	__try {
		RegFile->WriteInteger("Options", "Engine", EngineSpin->ValueAsInt);
		RegFile->WriteString("WiFi", "UDP", RFIDudpRX->Port);
		RegFile->WriteString("WiFi", "TCP", RFIDtcpRX->Port);
	    }
	__finally {
		// Deallocate RegFile
		delete RegFile;
		}
    }

if (bEngineDataLoaded && !bEngineDataCorrupt) { // don't save if it's not loaded or if we could not read it
    __try {
        TStringList * ptrEngDat = new TStringList();	// deleted in MainForm->CloseAllWindows()
        assert(ptrEngDat != NULL);		// make sure that construction worked
        ptrEngDat->Sorted = false;
        #if __BORLANDC__ >= 0x720
        ptrEngDat->Duplicates = System::Classes::dupIgnore;	// needed because of an ambiguity in Emabarcadero CB10.1
        #else
        ptrEngDat->Duplicates = dupIgnore;
        #endif
        try {
            // Save Engine Data to disk with overwrite
            assert(ED->Rows[ENG_LABEL_ROW]->Count == ENGDAT_COUNT);
            // transfer data from TSringGrid to TStringList
			ptrEngDat->Add(ED->Rows[ENG_LABEL_ROW]->CommaText);
            for (int EngNum = ENG_TOPROW ; EngNum < ED->RowCount ; EngNum++) {
                // Validate Data
                const int field = ValidateEngineData(ED, EngNum);
                if (field != -1) throw((EngNum * 100) + field);
                assert(ED->Rows[EngNum]->Count == ENGDAT_COUNT);
                // Engine Data is valid so move it into the EngDat Object
				// transfer data from TSringGrid to TStringList
				ptrEngDat->Add(ED->Rows[EngNum]->CommaText);
                }
            // write it
			ptrEngDat->SaveToFile(EngineDataFile);
            if (DEBUGGING) DB->Debug_Print("Engine Data Saved to " + EngineDataFile);
            if (ptrEngDat) {
                delete ptrEngDat;
                }
            }
            catch (int field) {
                DebugMessage("Edited Engine Data Field (" + String(field) + ") is Corrupt - Not Saved : " + EngineDataFile);
                bEngineDataCorrupt = true;
                CaughtErrors->Value ++;
                }
            catch (...) {
                DebugMessage("Edited Engine Data is Corrupt - Not Saved : " + EngineDataFile);
                bEngineDataCorrupt = true;
                CaughtErrors->Value ++;
                }
        }
	__finally {
		if (bEngineDataCorrupt) {
			bEngineDataLoaded = false;  // indicate data should be reloaded
			} else {
			bEngineDataLoaded = true; 	// the Engine data remains in place
			}
		}
    }
}
//---------------------------------------------------------------------------

int __fastcall TMainForm::ValidateEngineData(TStringGrid * ED, int EngNum)
{
// This function must be called within a try{} block because the ToInt()
// function will throw an error if it cannot parse an integer
//
assert(ED != NULL);
assert(EngNum >= 1 && EngNum <= ALLENGINE);	// 1-99 and 100 "All Engine Operation"
int val;

// Index
val = ED->Cells[ENG_NUM][EngNum].ToIntDef(0);
if (val != EngNum) return(ENG_NUM);
// Engine Name
val = ED->Cells[ENG_NAME][EngNum].Length();
if (val == 0) return(ENG_NAME);
if ((val == 1) && (ED->Cells[ENG_NAME][EngNum] != "*")) return(ENG_NAME);
//HDLT
//"Head Light" | ("Rule 17" << 1) | ("Bell on with Horn" << 2) | ("Auto Bell/Whistle" << 3)
val = ED->Cells[ENG_HDLT][EngNum].ToIntDef(0);
if (val < 0 || val > 31) return(ENG_HDLT);	// Headlight State (1 bit - mask: 0x01), Simulated Rule 17(1 bit - mask: 0x02) &
	// Bell on with Horn(1 bit - mask 0x04) & Auto Bell (1 bit - mask 0x08) & Auto Whistle (1 bit - mask 0x10)
//LIN
val = ED->Cells[ENG_PS][EngNum].ToIntDef(UDEF0);
if (val < UDEF0 || val > PS4) return(ENG_PS);
//Smoke
val = ED->Cells[ENG_SMOKE][EngNum].ToIntDef(0);
if (val != 0 && val != 1) return(ENG_SMOKE);
//FCC
val = ED->Cells[ENG_FCC][EngNum].ToIntDef(0);
if (val != 0 && val != 1) return(ENG_FCC);
//"Smoke Level (2 bits)" | ("Auto Labored Smoke" << 2)
val = ED->Cells[ENG_SMOKELEVEL][EngNum].ToIntDef(0);	// Smoke Level (2 bits - mask: 0x03) and Auto Labored Smoke (1 bit - mask: 0x04)
if (val < 0 || val > 7) return(ENG_SMOKELEVEL);
//Chat
val = ED->Cells[ENG_CHAT][EngNum].ToIntDef(0);
if (val != 0 && val != 1) return(ENG_CHAT);
//PC
val = ED->Cells[ENG_PROTOCHUFF][EngNum].ToIntDef(0);
if (val != 0 && val != 1) return(ENG_PROTOCHUFF);
//Rate
val = ED->Cells[ENG_CHUFFRATE][EngNum].ToIntDef(0);
if (val != 0 && val != 1) return(ENG_CHUFFRATE);
//Brake
val = ED->Cells[ENG_BRAKES][EngNum].ToIntDef(0);
if (val != 0 && val != 1) return(ENG_BRAKES);
//Ditch
val = ED->Cells[ENG_DITCH][EngNum].ToIntDef(0);
if (val < -1 || val > 3) return(ENG_DITCH);	// Ditch Light Combo Box (mask: 0x03)
//Auxiliary Lights 1
val = ED->Cells[ENG_AUX1][EngNum].ToIntDef(0);
if (val != 0 && val != 1) return(ENG_AUX1);
//Auxiliary Lights 2
val = ED->Cells[ENG_AUX2][EngNum].ToIntDef(0);
if (val != 0 && val != 1) return(ENG_AUX2);
//Beacon
val = ED->Cells[ENG_BEACON][EngNum].ToIntDef(0);
if (val != 0 && val != 1) return(ENG_BEACON);
//Auxiliary Lights 3
val = ED->Cells[ENG_AUX3][EngNum].ToIntDef(0);
if (val != 0 && val != 1) return(ENG_AUX3);
//Marker Light
val = ED->Cells[ENG_MARKER][EngNum].ToIntDef(0);
if (val != 0 && val != 1) return(ENG_MARKER);
//Type
val = ED->Cells[ENG_TYPE][EngNum].ToInt();
if (val != STEAM_ENGINE && val != DIESEL_ENGINE && val != ELECTRIC_ENGINE) return(ENG_TYPE);
//"Max Speed"
val = ED->Cells[ENG_MAXSPEED][EngNum].ToIntDef(0);
if (val < 0 || val > SPEEDMAX) return(ENG_MAXSPEED);
//Inactive
val = ED->Cells[ENG_STATE][EngNum].ToIntDef(INACTIVE);
if (val != ACTIVE && val != INACTIVE) return(ENG_STATE);
//Master
val = ED->Cells[VOL_MASTER][EngNum].ToIntDef(100);
if (val < 0 || val > VOLMAX) return(VOL_MASTER);
//Engine
val = ED->Cells[VOL_ENGINE][EngNum].ToIntDef(100);
if (val < 0 || val > VOLMAX) return(VOL_ENGINE);
//Accent
val = ED->Cells[VOL_ACCENT][EngNum].ToIntDef(100);
if (val < 0 || val > VOLMAX) return(VOL_ACCENT);
//Horn
val = ED->Cells[VOL_HORN][EngNum].ToIntDef(100);
if (val < 0 || val > VOLMAX) return(VOL_HORN);
//Bell
val = ED->Cells[VOL_BELL][EngNum].ToIntDef(100);
if (val < 0 || val > VOLMAX) return(VOL_BELL);
// TIU Number
val = ED->Cells[TIU_NUMBER][EngNum].ToIntDef(0);
if (val < 0 || val > TIUMAX) return(TIU_NUMBER);    // zero is valid and means no TIU has been set yet
//
//SP2 not checked
//SP3 not checked
return -1;	// valid
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::SetEngineActive1Click(TObject *Sender)
{
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
SetConsistState(EngineNo, ACTIVE);		// set active
// update users of the Engine List
UpdateEngDataUsers();
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::SetEngineInactive1Click(TObject *Sender)
{
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
SetConsistState(EngineNo, INACTIVE);		// set inactive
// update users of the Engine List
UpdateEngDataUsers();
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CMDButtonClick(TObject *Sender)
{
// Send an m4 command to all active engines 1-99 (DCS 2-100)
// (total of 99 engines)

for (int iEng = ENGINEMIN; iEng <= ENGINEMAX ; iEng++) {
   	if ((EngineData->Cells[ENG_NAME][iEng] != "*") && (EngineData->Cells[ENG_STATE][iEng] == ACTIVE)) {
        int MyTIUNo = EngineData->Cells [TIU_NUMBER] [iEng].ToIntDef(0);
        if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
            MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(iEng));
            return;
            }
        ptrTIUCommThread->Send_String_FIFO(iEng, "m4", MyTIUNo, RemoteNo, 11, SHORT_WAIT, NO_RECORD);
		Delay(m4DELAY);
		}
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::RelaySoundButtonClick(TObject *Sender)
{
PlaySound(MAKEINTRESOURCE(MOUSE_CLICK), HInstance, SND_RESOURCE | SND_ASYNC);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::TIUFeatureResetButtonClick(TObject *Sender)
{
// This function uses the TIU Spinner
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
assert(TIUSpin->ValueAsInt >= 1 && TIUSpin->ValueAsInt <= TIUMAX);
// if selected TIU is not connected fail
if (!TIUSelection[TIUSpin->ValueAsInt - 1]) {
	DebugMessagePopup("Warning", MainForm->TIUActiveMsg, "");
	return;
    }
// TIU Feature Reset of selected TIU in TIU Spinner
String FeatureResetMsg = "Are you sure you want to Feature Reset TIU " + String(TIUSpin->ValueAsInt) + "?";
int retv = Application->MessageBox(FeatureResetMsg.c_str(),	TEXT("Warning"), MB_YESNO | MB_ICONQUESTION);
if (retv == IDYES) {
    int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
    // Note: EngineNo is not used in the command and could possibily set to FACTORYRESET
	(void)ptrTIUCommThread->Send_String_FIFO(EngineNo, "F2", TIUSpin->ValueAsInt, RemoteNo, 11, VERY_LONG_WAIT * 20, RECORD_OK);
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::TIUFactoryResetButtonClick(TObject *Sender)
{
// This function uses the TIU Spinner
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
assert(TIUSpin->ValueAsInt >= 1 && TIUSpin->ValueAsInt <= TIUMAX);    
// if selected TIU is not connected fail
if (!TIUSelection[TIUSpin->ValueAsInt - 1]) {
	DebugMessagePopup("Warning", MainForm->TIUActiveMsg, "");
	return;
    }
// TIU Factory Reset of selected TIU in TIU Spinner
String FactoryResetMsg = "Are you sure you want to Factory Reset TIU " + String(TIUSpin->ValueAsInt) + "?";
int retv = Application->MessageBox(FactoryResetMsg.c_str(),	TEXT("Warning"), MB_YESNO | MB_ICONQUESTION);
if (retv == IDYES) {
    int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
    // Note: EngineNo is not used in the command and could possibily set to FACTORYRESET
	(void)ptrTIUCommThread->Send_String_FIFO(EngineNo, "F3", TIUSpin->ValueAsInt, RemoteNo, 11, SHORT_WAIT, NO_RECORD);
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::FormKeyDown(TObject *Sender, WORD &Key,
      TShiftState Shift)
{
// https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
// Make sure KeyPreview is true for this form
assert(KeyPreview);
switch (Key) {
	case 'D' :
    	if (Shift.Contains(ssCtrl) && Shift.Contains(ssShift)) {
    		DebugButton->OnClick(DebugButton);
            Key = 0;	// handled
			}
				// Added as menu shortcut
                else if (Shift.Contains(ssCtrl)) {
				ShowDetectedTagsClick(Sender);
            	Key = 0;	// handled
            	}
		break;
	case 'U' :
    	if (Shift.Contains(ssCtrl) && Shift.Contains(ssShift)) {
        	UtilitiesButton->OnClick(UtilitiesButton);
            Key = 0;	// handled
            }
    	break;
    // Added as menu shortcut
    case 'L' :
    	if (Shift.Contains(ssCtrl)) {
        	ShowLayoutWindowClick(Sender);
            Key = 0;	// handled
            }
    	break;
    case VK_F1:
        if (!Shift.Contains(ssCtrl) && !Shift.Contains(ssAlt) && EngineData->Cells[ENG_NAME][1] != "*") {
			// Set Engine 1 and call Startup
    	    EngineSpin->Value = 1;
            // if engine is active, call startup
            if (EngineData->Cells[ENG_STATE][1] == ACTIVE) { StartupButton->OnClick(StartupButton); }
    	   	break;
            }
		// if this is Ctrl-Fx and the LashUp is named then...
		if (Shift.Contains(ssCtrl) && (LashUp->LashUpData->Cells[LASHUP_NAME][1] != "*")) {
			// Set LashUp 1 and call Startup
    	    LashUp->LUpSpin->ValueAsInt = 1;
        	LashUp->LashUpStartUpButton->OnClick(LashUp->LashUpStartUpButton);
           	break;
            }
		if (!Shift.Contains(ssAlt)) Key = 0;	// handled unless it's in conjuction with ALT key
    	break;
    case VK_F2:
    	if (!Shift.Contains(ssCtrl) && !Shift.Contains(ssAlt) && EngineData->Cells[ENG_NAME][2] != "*") {
			// Set Engine 2 and call Startup
    	    EngineSpin->Value = 2;
            // if engine is active, call startup
            if (EngineData->Cells[ENG_STATE][2] == ACTIVE) { StartupButton->OnClick(StartupButton); }
            }
		// if this is Ctrl-Fx and the LashUp is named then...
		if (Shift.Contains(ssCtrl) && (LashUp->LashUpData->Cells[LASHUP_NAME][2] != "*")) {
			// Set LashUp 2 and call Startup
    	    LashUp->LUpSpin->ValueAsInt = 2;
        	LashUp->LashUpStartUpButton->OnClick(LashUp->LashUpStartUpButton);
           	break;
            }
        if (!Shift.Contains(ssAlt)) Key = 0;	// handled unless it's in conjuction with ALT key
    	break;
    case VK_F3:
    	if (!Shift.Contains(ssCtrl) && !Shift.Contains(ssAlt) && EngineData->Cells[ENG_NAME][3] != "*") {
			// Set Engine 3 and call Startup
    	    EngineSpin->Value = 3;
            // if engine is active, call startup
            if (EngineData->Cells[ENG_STATE][3] == ACTIVE) { StartupButton->OnClick(StartupButton); }
            }
		// if this is Ctrl-Fx and the LashUp is named then...
		if (Shift.Contains(ssCtrl) && (LashUp->LashUpData->Cells[LASHUP_NAME][3] != "*")) {
			// Set LashUp 3 and call Startup
    	    LashUp->LUpSpin->ValueAsInt = 3;
        	LashUp->LashUpStartUpButton->OnClick(LashUp->LashUpStartUpButton);
           	break;
            }
        if (!Shift.Contains(ssAlt)) Key = 0;	// handled unless it's in conjuction with ALT key
    	break;
    case VK_F4:
    	if (!Shift.Contains(ssCtrl) && !Shift.Contains(ssAlt) && EngineData->Cells[ENG_NAME][4] != "*") {
			// Set Engine 4 and call Startup
    	    EngineSpin->Value = 4;
            // if engine is active, call startup
            if (EngineData->Cells[ENG_STATE][4] == ACTIVE) { StartupButton->OnClick(StartupButton); }
            }
		// if this is Ctrl-Fx and the LashUp is named then...
		if (Shift.Contains(ssCtrl) && (LashUp->LashUpData->Cells[LASHUP_NAME][4] != "*")) {
			// Set LashUp 4 and call Startup
    	    LashUp->LUpSpin->ValueAsInt = 4;
        	LashUp->LashUpStartUpButton->OnClick(LashUp->LashUpStartUpButton);
           	break;
            }
        if (!Shift.Contains(ssAlt)) Key = 0;	// handled unless it's in conjuction with ALT key
    	break;
    case VK_F5:
    	if (!Shift.Contains(ssCtrl) && !Shift.Contains(ssAlt) && EngineData->Cells[ENG_NAME][5] != "*") {
			// Set Engine 5 and call Startup
    	    EngineSpin->Value = 5;
            // if engine is active, call startup
            if (EngineData->Cells[ENG_STATE][5] == ACTIVE) { StartupButton->OnClick(StartupButton); }
            }
		// if this is Ctrl-Fx and the LashUp is named then...
		if (Shift.Contains(ssCtrl) && (LashUp->LashUpData->Cells[LASHUP_NAME][5] != "*")) {
			// Set LashUp 5 and call Startup
    	    LashUp->LUpSpin->ValueAsInt = 5;
        	LashUp->LashUpStartUpButton->OnClick(LashUp->LashUpStartUpButton);
           	break;
            }
        if (!Shift.Contains(ssAlt)) Key = 0;	// handled unless it's in conjuction with ALT key
    	break;
    case VK_F6:
    	if (!Shift.Contains(ssCtrl) && !Shift.Contains(ssAlt) && EngineData->Cells[ENG_NAME][6] != "*") {
			// Set Engine 6 and call Startup
    	    EngineSpin->Value = 6;
            // if engine is active, call startup
            if (EngineData->Cells[ENG_STATE][6] == ACTIVE)  { StartupButton->OnClick(StartupButton); }
            }
		// if this is Ctrl-Fx and the LashUp is named then...
		if (Shift.Contains(ssCtrl) && (LashUp->LashUpData->Cells[LASHUP_NAME][6] != "*")) {
			// Set LashUp 6 and call Startup
    	    LashUp->LUpSpin->ValueAsInt = 6;
        	LashUp->LashUpStartUpButton->OnClick(LashUp->LashUpStartUpButton);
           	break;
            }
        if (!Shift.Contains(ssAlt)) Key = 0;	// handled unless it's in conjuction with ALT key
    	break;
    case VK_F7:
    	if (!Shift.Contains(ssCtrl) && !Shift.Contains(ssAlt) && EngineData->Cells[ENG_NAME][7] != "*") {
			// Set Engine 7 and call Startup
    	    EngineSpin->Value = 7;
            // if engine is active, call startup
            if (EngineData->Cells[ENG_STATE][7] == ACTIVE)  { StartupButton->OnClick(StartupButton); }
            }
		// if this is Ctrl-Fx and the LashUp is named then...
		if (Shift.Contains(ssCtrl) && (LashUp->LashUpData->Cells[LASHUP_NAME][7] != "*")) {
			// Set LashUp 7 and call Startup
    	    LashUp->LUpSpin->ValueAsInt = 7;
        	LashUp->LashUpStartUpButton->OnClick(LashUp->LashUpStartUpButton);
           	break;
            }
        if (!Shift.Contains(ssAlt)) Key = 0;	// handled unless it's in conjuction with ALT key
    	break;
    case VK_F8:
    	if (!Shift.Contains(ssCtrl) && !Shift.Contains(ssAlt) && EngineData->Cells[ENG_NAME][8] != "*") {
			// Set Engine 8 and call Startup
    	    EngineSpin->Value = 8;
            // if engine is active, call startup
            if (EngineData->Cells[ENG_STATE][8] == ACTIVE)  { StartupButton->OnClick(StartupButton); }
            }
		// if this is Ctrl-Fx and the LashUp is named then...
		if (Shift.Contains(ssCtrl) && (LashUp->LashUpData->Cells[LASHUP_NAME][8] != "*")) {
			// Set LashUp 8 and call Startup
    	    LashUp->LUpSpin->ValueAsInt = 8;
        	LashUp->LashUpStartUpButton->OnClick(LashUp->LashUpStartUpButton);
           	break;
            }
  		if (!Shift.Contains(ssAlt)) Key = 0;	// handled unless it's in conjuction with ALT key
    	break;
    case VK_F9:
    	if (!Shift.Contains(ssCtrl) && !Shift.Contains(ssAlt) && EngineData->Cells[ENG_NAME][9] != "*") {
			// Set Engine 9 and call Startup
    	    EngineSpin->Value = 9;
            // if engine is active, call startup
            if (EngineData->Cells[ENG_STATE][9] == ACTIVE)  { StartupButton->OnClick(StartupButton); }
            }
		// if this is Ctrl-Fx and the LashUp is named then...
		if (Shift.Contains(ssCtrl) && (LashUp->LashUpData->Cells[LASHUP_NAME][9] != "*")) {
			// Set LashUp 9 and call Startup
    	    LashUp->LUpSpin->ValueAsInt = 9;
        	LashUp->LashUpStartUpButton->OnClick(LashUp->LashUpStartUpButton);
           	break;
            }
        if (!Shift.Contains(ssAlt)) Key = 0;	// handled unless it's in conjuction with ALT key
    	break;
    case VK_F10:
    	if (!Shift.Contains(ssCtrl) && !Shift.Contains(ssAlt) && EngineData->Cells[ENG_NAME][10] != "*") {
			// Set Engine 10 and call Startup
    	    EngineSpin->Value = 10;
            // if engine is active, call startup
            if (EngineData->Cells[ENG_STATE][10] == ACTIVE)  { StartupButton->OnClick(StartupButton); }
            }
		// if this is Ctrl-Fx and the LashUp is named then...
		if (Shift.Contains(ssCtrl) && (LashUp->LashUpData->Cells[LASHUP_NAME][10] != "*")) {
			// Set LashUp 10 and call Startup
    	    LashUp->LUpSpin->ValueAsInt = 10;
			LashUp->LashUpStartUpButton->OnClick(LashUp->LashUpStartUpButton);
           	break;
            }
  		if (!Shift.Contains(ssAlt)) Key = 0;	// handled unless it's in conjuction with ALT key
    	break;
    case VK_F11:
        if (!Shift.Contains(ssCtrl) && !Shift.Contains(ssAlt) ) {
        	SetupButton->OnClick(SetupButton);
		    Key = 0;	// handled unless it's in conjuction with ALT or SHIFT key
            }
        if (Shift.Contains(ssCtrl)) {
        	StartUpVarControl(1);
		    Key = 0;	// handled
            }
    	break;
    case VK_F12:
    	if (!Shift.Contains(ssCtrl) && !Shift.Contains(ssAlt)) {
	    	QuickButton->OnClick(QuickButton);
			Key = 0;	// handled unless it's in conjuction with ALT or SHIFT key
        	}
        if (Shift.Contains(ssCtrl)) {
        	StartUpVarControl(2);
		    Key = 0;	// handled
            }
	   	break;
	}
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::FormKeyUp(TObject *Sender, WORD &Key,
      TShiftState Shift)
{
// https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
// Make sure KeyPreview is true for this form
assert(KeyPreview);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::StopAllButtonClick(TObject *Sender)
{
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
const TCursor OldCursor = Screen->Cursor; 	// save cursor
Screen->Cursor = crHourGlass;
// Send s0 command to all active engines in the engine list
for (int iEng = 1; iEng <= ENGINEMAX ; iEng++) {
    if (ptrOp[iEng] != NULL) {
	    // if the engine is running, use the speed scroller
	    // Speed 0
		ptrOp[iEng]->SpeedSlider->Value = 0;
        ptrOp[iEng]->SetSpeedSliderValue(0.0);				// causes the 's' command to be sent if the speed is not already zero
		ptrOp[iEng]->MotionSoundRequest->Checked = true;	// request sound on next train movement
       	} else {
        // Send s0 command directly to all active engines in the engine list (active and not running)
       	if (EngineData->Cells[ENG_NAME][iEng] != "*" && (EngineData->Cells[ENG_STATE][iEng] == ACTIVE)) {
            // NOTE: the TIU will NAK each speed command if the engine is not actually on the layout
            int MyTIUNo = EngineData->Cells [TIU_NUMBER] [iEng].ToIntDef(0);
            if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
                MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(iEng));
                return;
                }
            // Note: EngineNo is not used in the command and could possibily set to FACTORYRESET
			(void) ptrTIUCommThread->Send_String_FIFO(iEng, "s0", MyTIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK);	// Speed 0
           	}
        }
    }
Screen->Cursor = OldCursor;		// restore cursor
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::MuteAllClick(TObject *Sender)
{
// Toggle Mute Setting
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
MuteAll->Checked = !MuteAll->Checked;
if (MuteAll->Checked == true) {
	// mute all started engines not LashUps
    for (int iEng = 1; iEng <= ENGINEMAX ; iEng++) {
    	// if the Volume Control window is up, use that
        if (ptrVolume[iEng] != NULL) {
            ptrVolume[iEng]->EngineSoundOnSwitch->Checked = false;	// Mute
        	continue;	// on to next engine
            }
        // if the engine is started, Mute it
        if (ptrOp[iEng] != NULL) {
            int MyTIUNo = EngineData->Cells [TIU_NUMBER] [iEng].ToIntDef(0);
            if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
                MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(iEng));
                return;
                }
			// v0 = Master Volume, v1 = Engine Volume, v2 = Accent Volume, v3 = Horn Volume, v4 = Bell Volume
			char str[15];
			snprintf(str, sizeof str, "v1%03d", 0);
			(void) ptrTIUCommThread->Send_String_FIFO(iEng, str, MyTIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK);
			//
			snprintf(str, sizeof str, "v2%03d", 0);
			(void) ptrTIUCommThread->Send_String_FIFO(iEng, str, MyTIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK);
			//
        	}
    	}
    } else {
    // unmute all muted engines not LashUps
    for (int iEng = 1; iEng <= ENGINEMAX ; iEng++) {
    	// if the Volume Control window is up, use that
        if (ptrVolume[iEng] != NULL) {
            ptrVolume[iEng]->EngineSoundOnSwitch->Checked = true;	// unMute
        	continue;	// on to next engine
            }
        // if the engine is started, unMute it to volume value saved in the Engine Data file
        if (ptrOp[iEng] != NULL) {
            int MyTIUNo = EngineData->Cells [TIU_NUMBER] [iEng].ToIntDef(0);
            if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
                MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(iEng));
                return;
                }
			// v0 = Master Volume, v1 = Engine Volume, v2 = Accent Volume, v3 = Horn Volume, v4 = Bell Volume
			char str[15];
			snprintf(str, sizeof str, "v1%03d", EngineData->Cells[VOL_ENGINE][iEng].ToInt());
			(void) ptrTIUCommThread->Send_String_FIFO(iEng, str, MyTIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK);
			//
			snprintf(str, sizeof str, "v2%03d", EngineData->Cells[VOL_ACCENT][iEng].ToInt());
			(void) ptrTIUCommThread->Send_String_FIFO(iEng, str, MyTIUNo, RemoteNo, 11, SHORT_WAIT, RECORD_OK);
			//
        	}
    	}
    }
}
//---------------------------------------------------------------------------
/**
 * RX Decoding : RLL(1,0) to Binary
 *
 * RLL(1,0) to 8 bit Morton encoded to 8 bit binary
 * returns a -1 on fail
 */
int RXLookUp(unsigned int EncodedWord) {
  // RLL(1,0) to 8 bit Morton
  unsigned char b1 = cvt8B4B(EncodedWord >> 12) << 6;
  unsigned char b2 = cvt8B4B(EncodedWord >> 8) << 4;
  unsigned char b3 = cvt8B4B(EncodedWord >> 4) << 2;
  unsigned char b4 = cvt8B4B(EncodedWord);
  int MortonWord = b1 | b2 | b3 | b4;
  if (MortonWord == -1) return -1; // fail
  // 8 bit Morton to 8 bit binary
  return RXMorton(MortonWord);
}
//---------------------------------------------------------------------------
// part of 8B to 4B RLL(1,0) conversion
// returns a -1 on fail
int cvt8B4B(unsigned char b) {
  b = b & 0x0F;
  if (b == 0x0A) return 0x03;
  if (b == 0x09) return 0x02;
  if (b == 0x06) return 0x01;
  if (b == 0x05) return 0x00;
  return(-1);
}
//---------------------------------------------------------------------------
/**
 * TX encoding binary to RLL(1,0)
 *
 * 8 bit binary to 8 bit Morton Encoded to RLL(1,0)
 * returns a -1 on fail
 */
int TXLookUp(unsigned char BinaryWord) {
  // 8 bit binary to 8 bit Morton
  int MortonWord = TXMorton(BinaryWord);
  // 8 bit Morton to RLL(1,0)
  unsigned int b1 = cvt4B8B(MortonWord >> 6) << 12;
  unsigned int b2 = cvt4B8B(MortonWord >> 4) << 8;
  unsigned int b3 = cvt4B8B(MortonWord >> 2) << 4;
  unsigned int b4 = cvt4B8B(MortonWord);
  int EncodedWord = b1 | b2 | b3 | b4;
  if (EncodedWord == -1) return -1; // fail
  return EncodedWord;
}
//---------------------------------------------------------------------------
// part of RLL(1,0) 4B to 8B conversion
// returns a -1 on fail
int cvt4B8B(unsigned char b) {
  b = b & 0x03;
  if (b == 0x03) return 0x0A;
  if (b == 0x02) return 0x09;
  if (b == 0x01) return 0x06;
  if (b == 0x00) return 0x05;
  return(-1);
}
//---------------------------------------------------------------------------
// FROM:  https://fgiesen.wordpress.com/2009/12/13/decoding-morton-codes/
//---------------------------------------------------------------------------
// "Insert" a 0 bit after each of the 4 low bits of x
unsigned char Part1By1(unsigned char x) {
  x &= 0x0f;                  // x = ---- 3210
  x = (x ^ (x <<  2)) & 0x33; // x = --32 --10
  x = (x ^ (x <<  1)) & 0x55; // x = -3-2 -1-0
  return x;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
/*
 * Convert a Morton byte to binary
 */
unsigned char RXMorton(unsigned char MortonWord) {
  return (Part1By1((MortonWord & 0xF0) >> 4) << 1) | Part1By1(MortonWord & 0x0F);
}
//---------------------------------------------------------------------------
// Inverse of Part1By1 - "delete" all odd-indexed bits
unsigned char Compact1By1(unsigned char x) {
  x &= 0x55;                  // x = -3-2 -1-0
  x = (x ^ (x >>  1)) & 0x33; // x = --32 --10
  x = (x ^ (x >>  2)) & 0x0f; // x = ---- 3210
  return x;
}
//---------------------------------------------------------------------------
/*
 * Convert an 8 bit binary word to Morton
 */
unsigned char TXMorton(unsigned char BinaryWord) {
  return (Compact1By1(BinaryWord >> 1 ) << 4) | Compact1By1(BinaryWord);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CMTIUComm(TMessage &Message)
{
// Test Communications with TIU and set color of TIUSpin
const int TIU = Message.WParam;		// set TIU Number from Word Parameter
assert (TIU >= 1 && TIU <= TIUMAX);
//
if (IsTIUConnected()) {
	const TCursor OldCursor = Screen->Cursor; 	// save cursor
	Screen->Cursor = crHourGlass;
    if (!Setup->RFID_COM_Mode->Checked && !Setup->TXOnlyComm->Checked) {	// one port mode & two way comm required
	    if (GetTIUNoAIUCount(TIU)) { // read up the number of AIU
   			// if the GetTIUNoAIUCount worked, read up the TIU version number
			GetTIUVer(TIU);
	    	}
    	}
    Screen->Cursor = OldCursor;
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::BatteryStatusButtonClick(TObject *Sender)
{
// TODO : - Process and display the response to the Battery Status command. It does not yet display the correct indication Hi/OK/Low
//
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
if (Setup->Radio_COM_Mode && Setup->TXOnlyComm->Checked) {	// cannot do this function without full duplex communications
	DebugMessagePopup("Warning", MainForm->TXMsg, "");
    return;
    }

BatteryStatusButton->Caption = "Battery";

// Send Battery Status sequence (memory locations 0x82-0x83)
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
    return;
    }
unsigned short BATHOWord;
if (DEBUGGING) DB->Debug_Print("Battery Status Query", clGreen);
bool bretv = FetchWord(0x82, &BATHOWord, EngineNo, MyTIUNo);
if (!bretv) return;
String BATStr = "OK";						// Decoding is not correct
if ((BATHOWord & 0x00FF) > 0xD0) BATStr = "High";
if ((BATHOWord & 0x00FF) < 0x90) BATStr = "Low";
BatteryStatusButton->Caption = BATStr;
if (DEBUGGING) DB->Debug_Print("Battery Status : " + BATStr, clGreen);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::DTVButtonClick(TObject *Sender)
{
// It does not yet display the correct voltage
//
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
if (Setup->Radio_COM_Mode && Setup->TXOnlyComm->Checked) {	// cannot do this function without full duplex communications
	DebugMessagePopup("Warning", MainForm->TXMsg, "");
    return;
    }

DTVButton->Caption = "DTV";

// Send DTV sequence (memory locations 0x4C-0x4D)
unsigned short lVal;
if (DEBUGGING) DB->Debug_Print("DTV Query", clGreen);
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
    return;
    }
bool bretv = FetchWord(0x4C, &lVal, EngineNo, MyTIUNo);
if (!bretv) return;
const float RawValue = lVal;
float volts;
// Quadradic equation from cubic regression (volts = ax^2 + bx + c) of data points. See "CRC Analysis.wb3" page "DTV"
// RawValue = 8x^2 - 8x + 32		R^2 = 1.0 (Coefficient of Determination)
// Put equation into the form:  0 = 8x^2 - 8x + (32 - RawValue)
if (RawValue >= 32) {
	// Using the quadradic equation to solve:
	float a = 8;
	float b = -8;
	float c = 32 - RawValue;		// for RawValue >=32 (which is volts = 1.0)
	// then  b^2 - 4ac Only the positive root is valid
	float radicand = b*b - 4*a*c;
	volts = (-b + sqrt(radicand))/(2*a);
    } else {
    volts = 0.0;
    }

char DTVStr[64];
snprintf(DTVStr, sizeof DTVStr, "%4.1f V", volts);
DTVButton->Caption = DTVStr;
if (DEBUGGING) DB->Debug_Print("Track Voltage : " + String(DTVStr), clGreen);
}

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

void __fastcall TMainForm::DODButtonClick(TObject *Sender)
{
// read up the Odometer
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
if (Setup->Radio_COM_Mode && Setup->TXOnlyComm->Checked) {	// cannot do this function without full duplex communications
	DebugMessagePopup("Warning", MainForm->TXMsg, "");
    return;
    }

DODButton->Caption = "DOD";

// Get scaling factor Memory locations 0x08-0x09
unsigned short sVal;
if (DEBUGGING) DB->Debug_Print("Scaling Factor Query", clGreen);

int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
    return;
    }
bool bretv = FetchWord(0x08, &sVal, EngineNo, MyTIUNo);
if (!bretv) return;
const float fSF = sVal;
// Get DOD Memory locations 0x34-0x37
if (DEBUGGING) DB->Debug_Print("DOD Query", clGreen);
unsigned long lVal;
bretv = FetchInt(0x34, &lVal, EngineNo, MyTIUNo);
if (!bretv) return;
if (fSF == 0.0) return;		// prevent divide by zero
//const double DOD = (DODFACTOR * lVal)/fSF;	// multiplier found by experimentation;
const double fDOD = DODEQUATION(lVal, fSF);	// multiplier found by experimentation;
char DODStr[64];
snprintf(DODStr, sizeof DODStr, "%8.2f M", fDOD);
DODButton->Caption = DODStr;
if (DEBUGGING) {
	DB->Debug_Print("Odometer : " + String(DODStr), clGreen);
	DB->Debug_Print(Format((String)"DODButton=%.6f raw=%d DODFACTOR=%.6f ScaleFactor=%f ", ARRAYOFCONST((fDOD, (int)lVal, /*DODFACTOR*/ 512.0/DTOFACTOR, fSF))), clBlue);
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::DTOButtonClick(TObject *Sender)
{
// Read up the Trip Odometer
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
if (Setup->Radio_COM_Mode && Setup->TXOnlyComm->Checked) {	// cannot do this function without full duplex communications
	DebugMessagePopup("Warning", MainForm->TXMsg, "");
    return;
    }

DTOButton->Caption = "DTO";

// Fetch scaling factor Memory locations 0x08-0x09
unsigned short sVal;
if (DEBUGGING) DB->Debug_Print("Scaling Factor Query", clGreen);

int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
    return;
    }
bool bretv = FetchWord(0x08, &sVal, EngineNo, MyTIUNo);
if (!bretv) return;
const float fSF = sVal;
// Get DTO Memory locations 0x3C-0x3F
if (DEBUGGING) DB->Debug_Print("DTO Query", clGreen);
unsigned long lVal;
bretv = FetchInt(0x3C, &lVal, EngineNo, MyTIUNo);
if (!bretv) return;
if (fSF == 0.0) return;		// prevent divide by zero
//const double DTO = lVal/(fSF * DTOFACTOR);
const double fDTO = DTOEQUATION(lVal, fSF);
char DTOStr[64];
snprintf(DTOStr, sizeof DTOStr, "%10.4f M", fDTO);
DTOButton->Caption = DTOStr;
const double Sfeet = fDTO * 5280.0;
snprintf(DTOStr, sizeof DTOStr, "%15.2f Sfeet", Sfeet);
DTOButton->Hint = DTOStr;
if (DEBUGGING) {
	DB->Debug_Print("Trip Odometer : " + DTOButton->Caption + " (" + DTOButton->Hint + ")", clGreen);
	DB->Debug_Print(Format((String)"DTOButton=%.6f raw=%d DTOFACTOR=%.6f ScaleFactor=%f ", ARRAYOFCONST((fDTO, (int)lVal, DTOFACTOR, fSF))), clBlue);
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::FRMButtonClick(TObject *Sender)
{
//
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
// Read up the Maintenance Clock
if (Setup->Radio_COM_Mode && Setup->TXOnlyComm->Checked) {	// cannot do this function without full duplex communications
	DebugMessagePopup("Warning", MainForm->TXMsg, "");
    return;
    }

if (DEBUGGING) DB->Debug_Print("FRM Query", clGreen);
FRMButton->Caption = "FRM";

// Fetch FRM (memory locations 0x324-0x325)
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
    return;
    }
unsigned short x;
bool bretv = FetchWord(0x324, &x, EngineNo, MyTIUNo);
if (!bretv) return;
x = x >> 4; 	// divide by 16
char FRMStr[64];
if (x == 0) {
    snprintf(FRMStr, sizeof FRMStr, "Now");
    } else {
    if (x <= 16) {
        x = ((x - 1) * 160) + 60;
        } else {
        snprintf(FRMStr, sizeof FRMStr, "Invalid");
        }
    snprintf(FRMStr, sizeof FRMStr, "%d M", x);
    }
FRMButton->Caption = FRMStr;
if (DEBUGGING) DB->Debug_Print("Maintenance Required : " + String(FRMStr), clGreen);
// Send DOD sequences is done by the Remote. I don't know why
//DODButtonClick(Sender);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::DCHButtonClick(TObject *Sender)
{
    // ---------------------------------------------------------------------------------------
    // I believe that this calculation is correct. RTC result shows hours to be the same value
    // as the Remote but minutes and seconds are different. I believe there is an error
    // in the Remote's code.
    // ---------------------------------------------------------------------------------------
    // if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
// Read up the Chronometer
if (Setup->Radio_COM_Mode && Setup->TXOnlyComm->Checked) {	// cannot do this function without full duplex communications
	DebugMessagePopup("Warning", MainForm->TXMsg, "");
    return;
    }

if (DEBUGGING) DB->Debug_Print("DCH Query", clGreen);

DCHButton->Caption = "DCH";
// Fetch DCH (memory locations 0x38-0x3B)
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
    return;
    }
unsigned long lVal;
bool bretv = FetchInt(0x38, &lVal, EngineNo, MyTIUNo);
if (!bretv) return;
double t = lVal;
//t /= DCHFACTOR;	// estimated ticks per second
double fDCH = DCHEQUATION(t);	// estimated ticks per second
const int tt = static_cast<int> (fDCH);
const unsigned int hours = (tt/(60 * 60));
const unsigned int minutes = (tt/60) % 60;
const unsigned int seconds = tt % 60;
// ARRAYOFCONST needs to have the unsigned int cast to an int
DCHButton->Caption = Format((String)"%5dh:%2.2dm:%2.2ds", ARRAYOFCONST(((int)hours, (int)minutes, (int)seconds)));
if (DEBUGGING) {
	DB->Debug_Print("Chronometer : " + DCHButton->Caption, clGreen);
	DB->Debug_Print(Format((String)"DCHButton=%.6f raw=%d DCH FACTOR=%.6f", ARRAYOFCONST((fDCH, (int)lVal, DCHFACTOR))), clBlue);
    }
}
//---------------------------------------------------------------------------

bool __fastcall TMainForm::Query(char * CommandString, byte * QBuf, int Engine, int TIU, int Remote)
{
// Send a Query "q" or "qXXXX" command to the TIU and wait for a response, then display it on the Debug window
// return true on success or false on failure
bool retv = ptrTIUCommThread->Send_String_FIFO(Engine, CommandString, TIU, Remote, 21, QUERY_WAIT, NO_RECORD, NULL, NULL, NULL, QBuf);
if (retv && (QBuf != NULL)) {
	// dump from QBuf
	if ((DEBUGGINGX(1) && (strlen(CommandString) == 1)) || DEBUGGINGX(5)) byteBinaryDump(&QBuf[5], "Response to " + String(CommandString) + ":");
    }
return retv;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::byteBinaryDump(byte * Response, String Title)
{
if (DEBUGGINGX(1)) {
	DB->Debug_Print("Packet " + Title +
    	byteBinaryStringToHexString(Response, 4), clBlue);
    if (DEBUGGINGX(9)) {
		DB->Debug_Print("32 bit " + Title +
    		String(Response[0] << 24 | Response[1] << 16 | Response[2] << 8 | Response[3]), clBlue);
		DB->Debug_Print("16 bit " + Title +
			String(Response[0] << 8 | Response[1]) + " " +
			String(Response[2] << 8 | Response[3]), clBlue);
		DB->Debug_Print(" 8 bit " + Title +
			String(Response[0]) + " " +
			String(Response[1]) + " " +
			String(Response[2]) + " " +
			String(Response[3]), clBlue);
        }
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::sBinaryDump(String Response, String Title)
{
if (DEBUGGINGX(1)) {
	DB->Debug_Print("Packet " + Title +
    	sEncodedStringToHexString(Response), clRed);
	DB->Debug_Print("32 bit " + Title +
    	String(EncodedStringToDecimalInt(Response, 21, 16)), clRed);
	DB->Debug_Print("16 bit " + Title +
		String(EncodedStringToDecimalInt(Response, 21, 8)) + " " +
		String(EncodedStringToDecimalInt(Response, 29, 8)), clRed);
	DB->Debug_Print(" 8 bit " + Title +
		String(EncodedStringToDecimalInt(Response, 21, 4)) + " " +
		String(EncodedStringToDecimalInt(Response, 25, 4)) + " " +
		String(EncodedStringToDecimalInt(Response, 29, 4)) + " " +
		String(EncodedStringToDecimalInt(Response, 33, 4)), clRed);
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::EditTIUNumber1Click(TObject *Sender)
{
// TIU popup menu item
// This function uses the TIU Spinner
assert(TIUSpin->ValueAsInt >= 1 && TIUSpin->ValueAsInt <= TIUMAX);	// TIU 1-5
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
// if selected TIU is not connected fail
if (!TIUSelection[TIUSpin->ValueAsInt - 1]) {
	DebugMessagePopup("Warning", MainForm->TIUActiveMsg, "");
	return;
    }
NumberQuery->Caption = "Edit TIU #" + String(TIUSpin->ValueAsInt) + " Number";
NumberQuery->Question->Caption = "Select the New TIU Number";
NumberQuery->SpinLabel->Caption = "New TIU Number:";
NumberQuery->NumberSpin->MaxValue = TIUMAX;
NumberQuery->NumberSpin->MinValue = 1;
NumberQuery->NumberSpin->Value = TIUSpin->ValueAsInt;	// current TIU number
NumberQuery->NumberSpin->Visible = true;
#if __BORLANDC__ >= 0x720
// Defined for CB10.1 C++ programs that use the VCL.
NumberQuery->PopupMode = pmExplicit;
NumberQuery->PopupParent = MainForm;
NumberQuery->Position =  poDesigned;
#endif
int retv = NumberQuery->ShowModal();
if (retv == mrCancel) return;
// make sure the new TIU number slot is empty
int NewTIUNumber = NumberQuery->NumberSpin->Value;
int OldTIUNumber = TIUSpin->ValueAsInt;
if (OldTIUNumber == NewTIUNumber) return;		// no change
//
if (DEBUGGING) DB->Debug_Print("Changing TIU #" + String(OldTIUNumber) + " to #" + String(NewTIUNumber));

// send Bx command where x is the new TIU Number
unsigned char Command_String[12];
(void)snprintf((char *)Command_String, sizeof Command_String, "B%d", NewTIUNumber - 1);		// build command string with TIU #0-4
// NOTE: TIUNo in the Send_String command is the TIU to change to NewTIUNumber
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
// Note: EngineNo is not used in the command and could possibily set to FACTORYRESET
// Wait for first 11 characters of response (takes about 250 ms)
if (!ptrTIUCommThread->Send_String_FIFO(EngineNo, Command_String, OldTIUNumber, RemoteNo, 11, LONG_WAIT, NO_RECORD)) {
	DebugMessage("Failed to change TIU number (TIU " + String(OldTIUNumber) + " not found)");
    return;	// don't make any changes
    }
//
// Change array values from OldTIUNumber index to NewTIUNumber index
ptrTIUCommThread->LastEngine[NewTIUNumber - 1] = ptrTIUCommThread->LastEngine[OldTIUNumber - 1];
AIUCount[NewTIUNumber - 1] = AIUCount[OldTIUNumber - 1];
TIUVersion[NewTIUNumber - 1] = TIUVersion[OldTIUNumber - 1];
TIUVersionString[NewTIUNumber - 1] = TIUVersionString[OldTIUNumber - 1];
ptrDCS[NewTIUNumber - 1] = ptrDCS[OldTIUNumber - 1];
WTIUTXRX[NewTIUNumber - 1] = WTIUTXRX[OldTIUNumber - 1];
TIUSelection[NewTIUNumber - 1] = TIUSelection[OldTIUNumber - 1];
TIUSuperMode[NewTIUNumber - 1] = TIUSuperMode[OldTIUNumber - 1];
// clear array values from OldTIUNumber index
AIUCount[OldTIUNumber - 1] = 0;
TIUVersion[OldTIUNumber - 1] = 0;
TIUVersionString[OldTIUNumber - 1] = NULL;
ptrDCS[OldTIUNumber - 1] = NULL;
WTIUTXRX[OldTIUNumber - 1] = NULL;
TIUSelection[OldTIUNumber - 1] = false;
TIUSuperMode[OldTIUNumber - 1] = false;
// change the TIU number we are using - this calls TIUSpinValueChanged
// TODO : This causes a lot of side effects
TIUSpin->ValueAsInt = NewTIUNumber;

DebugMessage("Always press the [Read] button after you change a TIU number");
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ResetSetupButtonClick(TObject *Sender)
{
// Registry Reset
String RegistryResetMsg = "Are you sure you want to Reset all of your Preferences?";
int retv = Application->MessageBox(RegistryResetMsg.c_str(), TEXT("Warning"), MB_YESNO | MB_ICONQUESTION);
if (retv != IDYES) return;
//
// clear preferences and Exit
TRegIniFile * const RegFile = new TRegIniFile(RegFileRootKey);
assert(RegFile != NULL);		// make sure that construction worked
__try {
   	RegFile->EraseSection("Copyright");
   	RegFile->EraseSection("Debug");
   	RegFile->EraseSection("EngineInfo");
   	RegFile->EraseSection("EngineStatus");
   	RegFile->EraseSection("Layout");
   	RegFile->EraseSection("Operations");
	RegFile->EraseSection("Options");
   	RegFile->EraseSection("Program_Control");
	RegFile->EraseSection("Quick");
	RegFile->EraseSection("Signal");
	RegFile->EraseSection("Speed");
	RegFile->EraseSection("Tag");
   	RegFile->EraseSection("TrackSignal");
   	RegFile->EraseSection("WiFi");
  	RegFile->EraseSection("AIU");
  	RegFile->EraseSection("EngineList");
  	RegFile->EraseSection("LashUpList");
  	RegFile->EraseSection("MRU");
  	RegFile->EraseSection("RSList");
  	RegFile->EraseSection("TIU");
    }
__finally {
	delete RegFile;
	}
bSaveRegistryInfo = false;	// remind all forms not to save setting in Registry
Application->Terminate();	// FormClose does not run but Hide() in Setup does run
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::RTCHelpMenuClick(TObject *Sender)
{
// Help window - I use the IDE defined pointer so this Form will be
// automatically de-allocated when the program is terminated.
if (RTCHelp == NULL) {
	RTCHelp = new TRTCHelp(this);
	assert(RTCHelp != NULL);		// make sure that construction worked
    }
#if __BORLANDC__ >= 0x720
// Defined for CB10.1 C++ programs that use the VCL.
RTCHelp->PopupMode = pmExplicit;
RTCHelp->PopupParent = MainForm;
RTCHelp->Position =  poDesigned;
#endif
RTCHelp->Show();		// show Help window
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::SetAllEnginesActiveClick(TObject *Sender)
{
// Set All Engines Active 1-99 (DCS 2-100)
// (total of 99 engines)
//
for (int iEng = 1; iEng <= ENGINEMAX ; iEng++) {
   	if ((EngineData->Cells[ENG_NAME][iEng] != "*") && (EngineData->Cells[ENG_STATE][iEng] == INACTIVE)) {
        EngineData->Cells[ENG_STATE][iEng] = ACTIVE;
		}
    }
EngineData->Repaint();	// redraw
CountActiveEngines();
// update users of the Engine List
UpdateEngDataUsers();
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::SetAllEnginesInactiveClick(TObject *Sender)
{
// Set All Engines Active 1-99 (DCS 2-100)
// (total of 99 engines)
//
for (int iEng = 1; iEng <= ENGINEMAX ; iEng++) {
   	if ((EngineData->Cells[ENG_NAME][iEng] != "*") && (EngineData->Cells[ENG_STATE][iEng] == ACTIVE)) {
        EngineData->Cells[ENG_STATE][iEng] = INACTIVE;
		}
    }
EngineData->Repaint();	// redraw
CountActiveEngines();
// update users of the Engine List
UpdateEngDataUsers();
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::RSButtonClick(TObject *Sender)
{
assert(Setup != NULL);		// setup must be created
// LashUp Form is automatically created so that the LashUp List is in memory
RouteScene->Show();
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::SetupActivateRoutesandScenes1Click(TObject *Sender)
{
RSButton->OnClick(RSButton);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::DebugMessage(String MessageLine, String Caption, String StatusLine, int dly)
{
// show the message and print it to the debug window
DebugMessagePopup(Caption, MessageLine, StatusLine, dly);
if (DEBUGGING) DB->Debug_Print(MessageLine);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::DebugMessagePopup(String Caption, String MessageLine, String StatusLine, int dly)
{
// Show a message popup of message "Caption" with additional lines "MessageLine" and "StatusLine" for dly microseconds
// dly defaults to 2 seconds (2000 (UIPOPUPTIMEOUT) milliseconds) if not specified.
//
// don't show popup if running a Playback
if (bPlayingBack) return;
//
if (dly < 1) dly = 1;
if (dly > 10000) dly = 10000; 	// max of 10 seconds
//
// Keep message on the screen for dly milliseconds
// no buttons to push
// Start Thread
// FreeOnTerminate is set in constructor so we don't need to worry about freeing memory
TMessageDisplayThread * pThd = new TMessageDisplayThread(false, this, Handle, Caption, MessageLine, StatusLine, dly);
assert(pThd != NULL);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::AlreadyStartedClick(TObject *Sender)
{
// https://stackoverflow.com/questions/31649315/c-builder-button-click-function-reentry
LOCKEM()

// This can be called even if the Engine is already running. It just restores windows
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
// Allow Engines Only
assert(EngineNo >= (Setup->AllowZero->Checked? 0 : 1) && EngineNo <= ALLENGINE);	// 0 factory reset, 1-99, 100 "all engine operation"

// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
    return;
    }
EngineStartUp(EngineNo, MyTIUNo, true, 0);	// Engine is already started so don't send actual startup commands
return;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::RTCWebPage1Click(TObject *Sender)
{
//HINSTANCE retv = ShellExecute(NULL, "open", "http://www.silogic.com/trains/RTC_Running.html", NULL, NULL, SW_SHOWNORMAL);
// OR
HINSTANCE retv = ShellExecute(NULL, TEXT("open"), TEXT("http://www.silogic.com/trains/RTC_Running.html"), NULL, NULL, SW_SHOWNORMAL);
// OR
//HINSTANCE retv = ShellExecuteA(NULL, TEXT("open"), TEXT("http://www.silogic.com/trains/RTC_Running.html"), NULL, NULL, SW_SHOWNORMAL);
if ((int)retv <= 32) {
	// error
    if (DEBUGGING) DB->Debug_Print("Shell Execute error - return value = " + String((int) retv));
    }
}
//---------------------------------------------------------------------------

void __fastcall byteEncodeAString(Cmd * CSource, Cmd * CDest)
{
// Encode a string. Uses a CMD structure which includes the string ((char)Str) and a byte count (idx)
// encode a command from CSource into CDest - then the command can be sent - NOTE : CSource may contain 0x00
assert(CSource->idx < ((CMDMAX - 1 ) / 2));
// Sync Byte
CDest->Str[0] = 0x55;	// sync
CDest->Str[1] = 0x55;
// Encode the command
int i;
for (i = 0 ; i < CSource->idx ; i++) {
	int Encoded = TXLookUp(CSource->Str[i]);
	CDest->Str[2 + (2 * i)] = (Encoded & 0xFF00) >> 8;
    CDest->Str[3 + (2 * i)] = (Encoded & 0x00FF);
	}
// EOT
CDest->Str[2 + (2 * i)] = 0x4D;	// EOT
CDest->Str[3 + (2 * i)] = 0x00;	// NULL but not needed as CSource contains a length
CDest->idx = (CSource->idx * 2) + 3;
return;
}
//---------------------------------------------------------------------------

String __fastcall byteHextoString(byte * str)
{
String CM = "";
// str will not contain 0x00 character
int iLen = strlen(str);
//int iLen = strcspn(str, "M") + 1;	// locate the 0x4D
// convert byte string to a character String type for display in Buffer
for (int i = 0 ; i < iLen ; i++) {
    CM += IntToHex(str[i] & 0xFF, 2);
	}
return CM;
}
//---------------------------------------------------------------------------

String __fastcall TMainForm::byteExtractEngineName(byte * Resp)
{
// Extract the Engine Name from Response - always 16 characters
String EngineNamefromTIU = byteBinaryStringToASCIIString(&Resp[6], 16);
if (DEBUGGING) DB->Debug_Print("Engine Name: " + EngineNamefromTIU);
return EngineNamefromTIU;	// starting at character 6 for 16 characters
// Note: character 5 in the string contains the engine number
}
//---------------------------------------------------------------------------

String __fastcall byteBinaryStringToASCIIString(byte * Cmd, int Count)
{
// Revised with input from Grok
if (Cmd == NULL || Count <= 0)
        return "";
// Convert the binary string into a String
String DecodedString = "";
//  Convert
    for (int idx = 0 ; idx < Count; idx++) {
		byte val = Cmd[idx];
    	// print string byte in ASCII
        if (val >= 32 && val <= 127) {   // include SPACE and all characters up to 127
			DecodedString += (char)val;  // Convert to ASCII Character
            } else {
            DecodedString += " ";
            }
        }
return DecodedString;
}
//---------------------------------------------------------------------------

String show_binary(unsigned int u, int num_of_bits)
{
  if (num_of_bits <= 0) return "";  // handle error
  String a = "";
  //unsigned int t = pow(2.00, num_of_bits - 1);    // t is the max number that can be represented
  unsigned int t = 1u << (num_of_bits - 1);         // Bit shift is exact and fast

  //for(t; t>0; t = t/2)        // t iterates through powers of 2
  for (; t > 0; t >>= 1) {      // t iterates through powers of 2
      //if(u >= t){             // check if u can be represented by current value of t
      //    u -= t;
      if (u & t) {              // Bitwise AND is cleaner than comparison + subtraction
          a += "1";               // if so, add a 1
          } else {
          a += "0";               // if not, add a 0
      }
      }

  return a ;                     // returns string
}
#ifdef GROK
**Evaluation of the function:**

### What the function does
This is a C++ function (using Arduino `String` type) that converts an **unsigned integer** `u` into its **binary representation** as a `String`, using exactly `num_of_bits` bits (from MSB to LSB).

It builds the binary string **from left to right** (most significant bit first).

### Correctness
The logic is **mostly correct** and works well for typical use cases.

**Good points:**
- It correctly iterates from the highest power of 2 down to 1.
- Subtracts the bit value when possible and appends `'1'`, otherwise appends `'0'`.
- Produces the expected output for positive numbers within the bit range.

**Test results (verified):**
- `show_binary(5, 4)` ? `"0101"` ?
- `show_binary(10, 4)` ? `"1010"` ?
- `show_binary(42, 6)` ? `"101010"` ?
- `show_binary(255, 8)` ? `"11111111"` ?
- `show_binary(0, 8)` ? `"00000000"` ?

### Issues & Improvements

1. **Use of `pow(2.00, ...)` is problematic**
   - `pow` returns a `double`, which can cause **precision errors** for larger `num_of_bits` (e.g., = 53 bits on many systems).
   - You cast it to `unsigned int` implicitly, but it's fragile.

   **Better way:**
   ```cpp
   unsigned int t = 1u << (num_of_bits - 1);  // Bit shift is exact and fast
   ```

2. **The `for` loop syntax is odd**
   ```cpp
   for(t; t>0; t = t/2)
   ```
   This works but is non-standard. The initialization `t;` does nothing useful.

   **Cleaner version:**
   ```cpp
   for(unsigned int t = 1u << (num_of_bits - 1); t > 0; t >>= 1)
   ```

3. **No handling for `num_of_bits == 0`**
   - Would cause undefined behavior (shift by -1 or negative).

4. **Potential overflow / truncation**
   - If `u` is larger than what `num_of_bits` can represent (i.e., `u >= 2^num_of_bits`), the higher bits are simply lost (it shows only the lowest `num_of_bits` bits).
   - This is common behavior but should be documented or handled (e.g., with a warning).

5. **Minor performance**
   - Integer division `t = t/2` works, but right-shift `t >>= 1` is faster and clearer for powers of two.

### Recommended Improved Version

```cpp
String show_binary(unsigned int u, int num_of_bits)
{
    if (num_of_bits <= 0) return "";  // or handle error

    String a = "";
    unsigned int t = 1u << (num_of_bits - 1);   // Start at MSB

    for (; t > 0; t >>= 1)
    {
        if (u & t)          // Bitwise AND is cleaner than comparison + subtraction
            a += "1";
        else
            a += "0";
    }
    return a;
}
```

**Alternative (even cleaner) using bitwise AND:**
This version avoids modifying `u` and is very common:

//
String show_binary(unsigned int u, int num_of_bits)
{
    if (num_of_bits <= 0) return "";

    String a = "";
    for (int i = num_of_bits - 1; i >= 0; i--)
    {
        a += (u & (1u << i)) ? "1" : "0";
    }
    return a;
}
```

### Summary

- **Original function**: Works correctly for small `num_of_bits` (= 30–31), but has fragile floating-point usage and awkward loop.
- **Score**: 7.5 / 10 (functional but can be made more robust and idiomatic).
#endif
//---------------------------------------------------------------------------

String __fastcall byteBinaryStringToBinaryString(byte * Cmd, int Count)
{
// with changes from Grok
if (Count <= 0 || Cmd == NULL)
        return "";
// Convert the binary string into a String of '1' and '0'
// eg:
// From : {0x15,0x00,0x00,0x01}
// To : "00010101000000000000000000000001"
String ConvertedString = "";
//  Convert
	for (int idx = 0 ; idx < Count; idx++) {
        // convert byte to binary string
		ConvertedString += show_binary(Cmd[idx], 8);	// convert byte to binary string
        }
if (DEBUGGINGX(8)) DB->Debug_Print("Binary String: " + ConvertedString);
return ConvertedString;
}

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

String __fastcall byteBinaryStringToHexString(byte * Cmd, int Len)
{
// with changes from Grok
if (Len <= 0 || Cmd == NULL)
        return "";
// decode the encoded string into Hex
// string can contain 0x00 as a length is passed to this routine
// This routine must not call any VCL objects
// eg:
// From : {0x15,0x00,0x00,0x01}
// To : "15000001"
String ConvertedString = "";
//  Decode
    for (int idx = 0 ; idx < Len; idx++) {
        ConvertedString += IntToHex(Cmd[idx] & 0xFF, 2);
        }
if (DEBUGGINGX(8)) DB->Debug_Print("Hex String: " + ConvertedString);        
return ConvertedString;
}
#ifdef GROK
// Cleaner version using a temporary buffer (recommended for old BCB)
String __fastcall byteBinaryStringToHexString(byte * Cmd, int Len)
{
    if (Len <= 0 || Cmd == nullptr)
        return "";

    // Use a raw char buffer (safest for old versions)
    char* buffer = new char[Len * 2 + 1];   // +1 for safety

    static const char hex[] = "0123456789ABCDEF";

    for (int i = 0; i < Len; i++) {
        byte b = Cmd[i] & 0xFF;
        buffer[i*2]     = hex[b >> 4];
        buffer[i*2 + 1] = hex[b & 0x0F];
    }
    buffer[Len*2] = '\0';

    String result = buffer;   // construct AnsiString from null-terminated char*

    delete[] buffer;
    return result;
}
#endif
//---------------------------------------------------------------------------

String __fastcall byteCharacterStringToBinaryString(byte * Cmd, int Count)

{
// Convert the ASCII string into a String of '1' and '0'
// eg:
// From : "15,00,00,01"
// To : "00010101000000000000000000000001"
String ConvertedString = "";
unsigned int i1;
//  Convert
	for (int idx = 0 ; idx < Count; idx++) {
        // convert characters eg: "08" to 0x08
		int retv = sscanf(&Cmd[idx * 3], "%x", &i1);   // %x requires &int
        assert(retv == 1);
        // convert hex to binary string eg: 0x08 to "00001000"
		ConvertedString += show_binary(i1, 8);	// convert byte to binary string
        }
if (DEBUGGINGX(8)) DB->Debug_Print("byteCharacterStringToBinaryString : " + ConvertedString);
return ConvertedString;
}
//---------------------------------------------------------------------------

void __fastcall byteCharacterStringTobyteHexString(byte * Cmd, byte * Result, int Count)
{
// Convert comma separated ASCII hex string into a comma separated binary Hex string
// Cmd can contain 0x00 as a length is passed to this routine
// This routine must not call any VCL objects
// eg:
// From : "15,00,00,01"     always two digits per hex number
// To : {0x15, 0x00, 0x00, 0x01}
unsigned int i1;
//  Decode
    for (int idx = 0 ; idx < Count; idx++) {
        // convert characters eg: "08" to 0x08
		int retv = sscanf(&Cmd[idx * 3], "%x", &i1);   // %x requires &int
        assert(retv == 1);
        // save result
        Result[idx] = i1;
        }
return;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::HotButtonClick(TObject *Sender)
{
DoHotButton(HotButtonPopup->Items->Items[LastHotComPage - 1], LastHotComPage);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::DoHotButton(TObject * Sender, int Page)
{
// Create Hot Button page 1 to HOTPAGEMAX
//
assert(Page >= 1 && Page <= HOTPAGEMAX);
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
    return;
    }
// This function uses the TIU and AIU Spinners
// This code should work with auto create on OR off.
// ------------ With programmed create using C++ Builder's generated pointer
// Hot Command Form should be manually created as it is rarely needed
if (!ptrHot_Command[Page-1]) {
    ptrHot_Command[Page-1] = new THot_Command(this, RemoteNo, EngineNo, MyTIUNo, AIUSpin->ValueAsInt, Page);   // create the Form & Pass "this" as the owner
    assert(ptrHot_Command[Page-1] != NULL);			// make sure that construction worked
    ptrHot_Command[Page-1]->ParentHandle = Handle; 	// for return messages
   	}
ptrHot_Command[Page-1]->Show();

if (Sender) {
	reinterpret_cast<TMenuItem *>(Sender)->Checked = true;	// add check mark to Hot Buttons Menu
	}

LastHotComPage = Page;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::HotButtons1Click(TObject *Sender)
{
DoHotButton(HotButtons1, 1);		// Page 1 (Default)
}
//---------------------------------------------------------------------------
void __fastcall TMainForm::HotButtons2Click(TObject *Sender)
{
DoHotButton(HotButtons2, 2);		// Page 2
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::HotButtons3Click(TObject *Sender)
{
DoHotButton(HotButtons3, 3);		// Page 3
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::HotButtons4Click(TObject *Sender)
{
DoHotButton(HotButtons4, 4);		// Page 4
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::HotButtons5Click(TObject *Sender)
{
DoHotButton(HotButtons5, 5);		// Page 5
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CMHotCom(TMessage &Message)
{
// Open a page of Hot Buttons
int Page = Message.WParam;
assert(Page >= 1 && Page <= HOTPAGEMAX);
DoHotButton(HotButtonPopup->Items->Items[Page - 1], Page);
}

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

void __fastcall TMainForm::ReadEEPROMButtonClick(TObject *Sender)
{
// note: this command works from the DCS Loader running on a PC. Does not work on the Radio Comm.
// It does not work on the WIU/TIU.
// It does work on the WTIU v1.04. The 'R' command works but I don't have the decoding
// of the return worked out yet.
//
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
// Only works in WiFi Mode - need to narrow this check to the WTIU only
if (!Setup->WTIU_WiFi_Mode->Checked) {
	DebugMessagePopup("Warning", MainForm->WTIUOnlyMsg, "");
	return;
    }
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
//
char cmd[25];
int iTIUCount;
char RResp[4096 + 25];
int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
    return;
    }
//
// try to read Engine Data from EEPROM at 0x0 for 16 bytes
snprintf(cmd, sizeof cmd, "R%06X%06X", 0, 16);
(void) ptrTIUCommThread->Send_String_FIFO(EngineNo, cmd, MyTIUNo, RemoteNo, 11 + 16, MED_WAIT, NO_RECORD, NULL, NULL, &iTIUCount, RResp); 	// send 'R' command to the TIU

// try to read file name from EEPROM at 0x1E98  for 0x3D bytes
snprintf(cmd, sizeof cmd, "R%06X%06X", 0x1E98, 0x3D);
(void) ptrTIUCommThread->Send_String_FIFO(EngineNo, cmd, MyTIUNo, RemoteNo, 11 + 0x3D, MED_WAIT, NO_RECORD, NULL, NULL, &iTIUCount, RResp); 	// send 'R' command to the TIU
String FileName = byteCharacterStringToCharacterString(&RResp[13] , 0x3D);
DB->Debug_Print("File Name :             " + FileName);
//
// try to read Engine name from EEPROM at 0x1E24 for 16 bytes
snprintf(cmd, sizeof cmd, "R%06X%06X", 0x1E24, 16);
(void) ptrTIUCommThread->Send_String_FIFO(EngineNo, cmd, MyTIUNo, RemoteNo, 11 + 16, MED_WAIT, NO_RECORD, NULL, NULL, &iTIUCount, RResp); 	// send 'R' command to the TIU
String EngineName = byteCharacterStringToCharacterString(&RResp[13] , 16);
DB->Debug_Print("Engine Name :           " + EngineName);
//
// try to read all data from EEPROM at 0x1DD2 for 0xF6 bytes
snprintf(cmd, sizeof cmd, "R%06X%06X", 0x1DD2, 0xF6);
(void) ptrTIUCommThread->Send_String_FIFO(EngineNo, cmd, MyTIUNo, RemoteNo, 11 + 0xF6, MED_WAIT, NO_RECORD, NULL, NULL, &iTIUCount, RResp); 	// send 'R' command to the TIU
String EngineData = byteCharacterStringToCharacterString(&RResp[13], 0xF6);
DB->Debug_Print("All Data :              " + EngineData);
}
//---------------------------------------------------------------------------

String __fastcall byteCharacterStringToCharacterString(byte * Cmd, int Count)

{
// Convert the ASCII string into a String characters
// eg:
// From : "264C452052532D332023383335362020"
// To : "P&LE......"
String ConvertedString = "";
unsigned int i1;
String sVal;
//  Convert
	for (int idx = 0 ; idx < Count; idx++) {
        // convert characters eg: "08" to 0x08
		int retv = sscanf(&Cmd[idx * 2], "%02x", &i1);   // %x requires &int
        if (retv != 1) {    // ignore invalid Hex characters
            ConvertedString += String(".*");
            continue;
            }
        // convert hex to binary string
		sVal = String((char)i1);	// convert byte to binary string
    	ConvertedString += String('.') + sVal;
        }
if (DEBUGGINGX(8)) DB->Debug_Print("byteCharacterStringToCharacterString : " + ConvertedString);
return ConvertedString;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CopyrightClick(TObject *Sender)
{
// Copy the Form to the clipboard.
// In BCB4.0, there is no SetSize method associated with TBitmap
// so we have to use Width and Height separately.
//
// see:
// https://stackoverflow.com/questions/3190777/how-do-i-copy-a-form-as-an-image-to-the-clipboard
//
Graphics::TBitmap * bm;
bm = new Graphics::TBitmap;
try
	{
    #if __BORLANDC__ >= 0x720
	// Defined for CB10.1 C++ programs that use the VCL.
	bm->SetSize(ClientWidth, ClientHeight);
    #else
    bm->Width = ClientWidth;
    bm->Height = ClientHeight;
    #endif
	BitBlt(bm->Canvas->Handle, 0, 0, ClientWidth, ClientHeight, Canvas->Handle, 0, 0, SRCCOPY);
    Clipboard()->Assign(bm);
    }
__finally
	{
	delete bm;
    }
}
//---------------------------------------------------------------------------
// Delay
//
// Waits the specified amount of time while processing messages
// Uses MsgWaitForMultipleObjects()
//
// Parameters:
// dwDelayMs - number of milliseconds to wait  0 < dwDelayMs <= 100000
//
// Returns:
// no return value.
//
// https://stackoverflow.com/questions/77852665/is-there-a-windows-api-function-like-sleep-that-works-like-messageboxa
// by Remy Lebeau
// https://stackoverflow.com/questions/31026470/delphi-sleep-without-freeze-and-processmessages
//
void __fastcall TMainForm::Delay(DWORD dwDelayMs)
{
assert (dwDelayMs >= 0 && dwDelayMs <= 100000);
//
if (DEBUGGING && (dwDelayMs > 99)) {
	DB->Debug_Print("Delay " + String(dwDelayMs));
    }
// Cannot use Sleep in the main Thread because the UI relies on messages
DWORD dwStart, dwNow, dwWait;
//
dwStart = GetTickCount();
for (;;) {
	// Wait for a any message to show up
	dwWait = MsgWaitForMultipleObjects(0, NULL, FALSE, dwDelayMs,
		QS_KEY | QS_PAINT | QS_POSTMESSAGE | QS_SENDMESSAGE | QS_TIMER | QS_MOUSEMOVE | QS_HOTKEY);
	if (bEmergencyStop) break;	// get out in a hurry
	//
	if (dwWait == WAIT_TIMEOUT) {	// Timed out?
    	break;
        }
	MyProcessMessages();		// process all messages EXCEPT mouse button messages
                                // Calls PeekMessage(), TranslateMsg() and DispatchMessage()
	dwNow = GetTickCount(); 	// adjust the delay time remaining
	dwStart = dwNow - dwStart;
	if (dwStart >= dwDelayMs) break;
	dwDelayMs -= dwStart;
	dwStart = dwNow;
	if (bEmergencyStop) break;	// get out in a hurry
    }
return;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::MyProcessMessages(void)
{
// replacement for the Application->ProcessMessages() routine
// this routine processs all messages except mouse button messages
//
// https://stackoverflow.com/questions/41539678/are-event-handlers-re-entrant-in-embarcadero-cbuilder?rq=1
/*
typedef struct MSG {     // msg
    HWND   hwnd;
    UINT   message;
    WPARAM wParam;
    LPARAM lParam;
    DWORD  time;
    POINT  pt;
};
//
// Pump the message queue for all messages except mouse button messages
// from 513 to 521 (0x0201 to 0x0209)

0000 		000 		WM_NULL
...
0200 		512 		WM_MOUSEFIRST
0200 		512 		WM_MOUSEMOVE
0201 		513 		WM_LBUTTONDOWN
0202 		514 		WM_LBUTTONUP
0203 		515 		WM_LBUTTONDBLCLK
0204 		516 		WM_RBUTTONDOWN
0205 		517 		WM_RBUTTONUP
0206 		518 		WM_RBUTTONDBLCLK
0207 		519 		WM_MBUTTONDOWN
0208 		520 		WM_MBUTTONUP
0209 		521 		WM_MBUTTONDBLCLK
0209 		521 		WM_MOUSELAST
020a 		522 		WM_MOUSEWHEEL
*/
bool MsgAvailable;
MSG msg;
while (true) {
	MsgAvailable = PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE);	// message available ? but leave in queue
   	if (!MsgAvailable) break;	// no messages available
   	if (msg.message <= WM_MOUSEMOVE) {	// Is it a non mouse button message?
       	// Message from WM_NULL to and including WM_MOUSEMOVE
       	GetMessage(&msg, NULL, WM_NULL, WM_MOUSEMOVE);	// and remove from the queue
		TranslateMessage(&msg);
		DispatchMessage(&msg);
        continue;	// check for another message
        }

  	if (msg.message >= (WM_MOUSELAST+1)) {	// Is it a non mouse button message?
    	// Display the message Base and Offset if Debug is on
       	// Message from WM_MOUSELAST+1 to the last message possible, skipping a few that occur but we don't care to see
        if (DEBUGGINGX(5)) {
	        int MsgBase, MsgOffset;
	        // first decode the message base and offset
		    if (msg.message >= 0x8000) {	// WM_APP = 0x8000
            	// Application Message
    		    MsgBase = WM_APP;
        		MsgOffset = msg.message - WM_APP;
		    	} else {
    		    // Windows Message
				MsgBase = 0x0000;
	        	MsgOffset = msg.message;
	    	    }
    	    //
	    	if ((msg.message != 15) &&
            	(msg.message != (CM_TIUCHAR)) &&	    // CM_TIUCHAR
            	(msg.message != (CM_RFIDASCIICHAR)) &&  // CM_RFIDASCIICHAR
            	(msg.message != (CM_UI)) &&	            // CM_UI
            	(msg.message != (CM_TIUASCIICHAR))		// CM_TIUASCIICHAR
                	) {
                DB->Debug_Print("MyProcessMessages: 0x" + IntToHex(MsgBase, 4) + ":0x" + IntToHex(MsgOffset, 4));
                }
            }
       	GetMessage(&msg, NULL, WM_MOUSELAST+1, 0xFFFFFFFF);	// and remove from the queue
		TranslateMessage(&msg);
		DispatchMessage(&msg);
        continue;	// check for another message
        }
    // if all that's left is mouse button messages, get out
    if (msg.message > WM_MOUSEMOVE && msg.message <= WM_MOUSELAST) break;
    }
return;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::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) {
   		if (DEBUGGING) DB->Debug_Print("Test: Error: " + ConnectedWiFiRadioLED->Caption + " Not a Hex Digit : " + String((char *)Command), clRed);
        return;
        }
    char b = Command[i + 1];
    const char* q = std::lower_bound(lut, lut + 16, b);
    if (*q != b) {
    	if (DEBUGGING) DB->Debug_Print("Test: Error: " + ConnectedWiFiRadioLED->Caption + " Not a Hex Digit : " + String((char *)Command), clRed);
        return;
        }
	Packet[i/2] = (((p - lut) << 4) | (q - lut));
	}
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::RestorethescrollbarontheEngineList1Click(
      TObject *Sender)
{
EngineData->ScrollBars = ssBoth;
ActEngLst->ScrollBars = ssBoth;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::DecodeStringsButtonClick(TObject *Sender)
{
// Decode all strings in the Buffer and write them to the CRC Buffer
//
// this does the same thing as the popup menu item but only from the TIU Buffer
//
if (DEBUGGING) STOPem->Enabled = true;
bStop = false;
//
const int count = TIUBuffer->Lines->Count;

for (int idxdb = 0 ; idxdb < count ; idxdb++) {
   if (bStop) {
        break;
        }
	String MyString = TIUBuffer->Lines->Strings[idxdb];
    CRCBuffer->SelAttributes->Color = TIUBuffer->SelAttributes->Color;	// Copy with same color
   	CRCBuffer->Lines->Append(MyString);
    MyString = CleanUpCommand(MyString);
	ScrollEnd(CRCBuffer);   // remain at end of buffer
	(void) sDecodeString(MyString, true); // Decode and print (into CRC Buffer) the string in MyString
    Application->ProcessMessages();
	}
STOPem->Enabled = false;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::FormActivate(TObject *Sender)
{
#if __BORLANDC__ >= 0x720      // CB10.1 only
AlphaBlend = false; // Remove Transparency
#else
// Remove WS_EX_LAYERED from this window styles
SetWindowLong(Handle, GWL_EXSTYLE, GetWindowLong(Handle, GWL_EXSTYLE) & ~WS_EX_LAYERED);
#endif
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::FormDeactivate(TObject *Sender)
{
if (!Setup) return;
if (Setup->TransparentSpin->Value == Setup->TransparentSpin->MaxValue) return;
if (LocalTransparencyOverride) return;
#if __BORLANDC__ >= 0x720      // CB10.1 only
AlphaBlendValue = (255 * Setup->TransparentSpin->Value) / 100;
AlphaBlend = true;  // Add Transparency
#else
// Set WS_EX_LAYERED on this window
SetWindowLong(Handle, GWL_EXSTYLE, GetWindowLong(Handle, GWL_EXSTYLE) | WS_EX_LAYERED);
//SetLayeredWindowAttributes
m_pSetLayeredWindowAttributes(Handle, NULL, (255 * Setup->TransparentSpin->Value) / 100, LWA_ALPHA);
#endif

}
//---------------------------------------------------------------------------

void __fastcall TMainForm::Engine_List_LabelClick(TObject *Sender)
{
LocalTransparencyOverride = !LocalTransparencyOverride;	// Toggle transparency locally
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::AIUSpinValueChanged(TObject *Sender)
{
// Called when the user clicks on the AIU up/down buttons
static LastAIUNo = 0;
// This function uses the TIU Spinner and the AIU Spinner
//if (LastAIUNo == AIUSpin->ValueAsInt) return; // no message unless the ValueAsInt changes
if (DEBUGGING) DB->Debug_Print("TIU #" + String(TIUSpin->ValueAsInt) + " AIU# changed from " + String(LastAIUNo) + " to " + String(AIUSpin->ValueAsInt) +
    " - Total AIU " + String(AIUCount[TIUSpin->ValueAsInt - 1]));
LastAIUNo = AIUSpin->ValueAsInt;
AIUSpin->MaxValue = AIUCount[TIUSpin->ValueAsInt - 1];   // AIUSpinValueChanged() max value from 'x' command
AIUSpinChange(Sender);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::AIUSpinChange(TObject *Sender)
{
if (bTIUConnected == true) {
    if (TIUSelection[TIUSpin->ValueAsInt - 1] == true && AIUSpin->ValueAsInt <= AIUCount[TIUSpin->ValueAsInt - 1]) {
        AIUSpin->ColorDefault = clLime;	// set AIU Spinner color to green
        } else {
        AIUSpin->ColorDefault = clRed;	// set AIU Spinner color to red
        }
    } else {
    AIUSpin->ColorDefault = clYellow;	// set AIU Spinner color to Yellow
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::TIUSpinValueChanged(TObject *Sender)
{
// Called when the user clicks on the TIU up/down buttons
static LastTIUNo = 1;
// This function uses the TIU Spinner and the AIU Spinner
//
//if (LastTIUNo == TIUSpin->ValueAsInt) return; // no message unless the ValueAsInt changes
if (DEBUGGINGX(3)) DB->Debug_Print("TIU Number changed from " + String(LastTIUNo) + " to " + String(TIUSpin->ValueAsInt));
AIUSpin->MaxValue = AIUCount[TIUSpin->ValueAsInt - 1];   // TIUSpinValueChanged() max value from 'x' command
AIUSpin->ValueAsInt = AIUSpin->MaxValue > 0 ? 1 : 0;
LastTIUNo = TIUSpin->ValueAsInt;
if (TIUSelection[TIUSpin->ValueAsInt - 1]) {
    MainForm->ConnectButton->Caption = TIUVersionString[TIUSpin->ValueAsInt - 1];
    } else {
    MainForm->ConnectButton->Caption = "&Connect";
    }
TIUSpinChange(Sender);
AIUSpinChange(Sender);  // keep both spinner colors up to date
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::TIUSpinChange(TObject *Sender)
{
if (bTIUConnected == true) {
    if (TIUSelection[TIUSpin->ValueAsInt - 1] == true) {
        TIUSpin->ColorDefault = clLime;	// set TIU Spinner color to green
        AIUSpin->ColorDefault = clLime;	// set AIU Spinner color to green
        } else {
        TIUSpin->ColorDefault = clRed;	// set TIU Spinner color to red
        AIUSpin->ColorDefault = clRed;	// set AIU Spinner color to red
        }
    } else {
    TIUSpin->ColorDefault = clYellow;	// set TIU Spinner color to Yellow
    AIUSpin->ColorDefault = clYellow;	// set AIU Spinner color to Yellow
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::RemoteSpinValueChanged(TObject *Sender)
{
// The remote number should be different than any actual remote that
// you have. I suggest using 15.
if (DEBUGGING) DB->Debug_Print("Remote Number changed from " + String(RemoteNo) + " to " + String(RemoteSpin->Value));
// besides initalization and command line, the next line is the only place that RemoteNo is changed.
RemoteNo = RemoteSpin->Value;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CMUI(TMessage &Message)
{
//
// Messages from threads to do some processing in the VCL
//
int msgtype = Message.WParam;
UI_Struct * MessageX = (UI_Struct *) Message.LParam;

switch (msgtype) {
	case M_PrintItem:
        // All Debug Prints come through here
		assert(DB); // must be created
        //        //
		// Message to RichEdit Control
		DB->Debug_Messages->SelAttributes->Color = MessageX->PrintColor;
		//DB->Debug_Messages->SelStart = DB->Debug_Messages->GetTextLen();
		//DB->Debug_Messages->SelText = String(MessageX->Line) + TEXT("\n");
		DB->Debug_Messages->Lines->Add(MessageX->Line);
		DB->Debug_Messages->SelAttributes->Color = clBlack;	// reset color to Black
		// remain at end of buffer
		//
        if (DB->AutoScroll->Checked) {
            ScrollEnd(DB->Debug_Messages);    	// remain at end of Buffer
        	}
        delete MessageX;	// delete the Message after processing it
    	break;

    case M_Bump:
		if (MessageX->Countr != NULL) MessageX->Countr->Value += 1;
		delete MessageX;	// delete the Message after processing it
		break;

    case M_Dec:
		if (MessageX->Countr != NULL) MessageX->Countr->Value -= 1;
        delete MessageX;	// delete the Message after processing it
 	    break;

    case M_SetColor:
    	if (MessageX->Spinner != NULL) MessageX->Spinner->ColorDefault = MessageX->ColorDefault;
        delete MessageX;	// delete the Message after processing it
		break;

    case M_SetTextValue:
    	if (MessageX->EditBox != NULL) MessageX->EditBox->Text = MessageX->Line;
        delete MessageX;	// delete the Message after processing it
        break;

    case M_GetTextValue:
    	Application->ProcessMessages();		// make sure that VCL component EditBox is up to date (this worked!)
        if (MessageX->EditBox != NULL)
        	MessageX->Line = MessageX->EditBox->Text;	// return text to Comm_Thread
            else
            MessageX->Line = "Data not Available";
        // MessageX struct is deleted in Comm_Thread.cpp after the return value is read
    	break;

    case M_SetRecordingTextValue:
		//RecordingBuffer->Lines->Append(MessageX->Line);
        RecordingBuffer->SelStart = RecordingBuffer->GetTextLen();
        RecordingBuffer->SelText = MessageX->Line + TEXT("\r\n");;
        ScrollEnd(RecordingBuffer);    	// remain at end of Buffer
		delete MessageX;	// delete the Message after processing it
		break;

    case M_ShowinTIUBuffer:
        if (TIULog->Checked) {
            TIUBuffer->SelAttributes->Color = MessageX->ColorDefault;
            // Display it in buffer
            TIUBuffer->SelStart = TIUBuffer->GetTextLen();
            if (MessageX->ColorDefault == clLime) {
                TIUBuffer->SelText = TEXT("\r\n") + String(MessageX->Line) + TEXT("\r\n");
                TIULineCount->Caption = String(TIULineCount->Caption.ToInt() + 1);
                } else {
                TIUBuffer->SelText = String(MessageX->Line) + TEXT("\r\n");
                }
            TIULineCount->Caption = String(TIULineCount->Caption.ToInt() + 1);
            TIUBuffer->SelAttributes->Color = clBlack;		// restore color to Black
            ScrollEnd(TIUBuffer);    	// remain at end of TIU Buffer

            if (RFIDLog->Checked && Setup->Echo->Checked) {
                RFIDBuffer->SelStart = RFIDBuffer->GetTextLen();
                RFIDBuffer->SelText = String(MessageX->Line) + TEXT("\r\n");
                ScrollEnd(RFIDBuffer);    	// remain at end of Buffer
                }
            }
        delete MessageX;	// delete the Message after processing it
		break;

    case M_PopupItem:
    	DebugMessagePopup("RTC Information", MessageX->Line, MessageX->Status, MessageX->iValue);	// iValue is time to display in milliseconds
        delete MessageX;	// delete the MessageX after processing it
        break;

    case M_ShowinBuffer:
    	if (MessageX->Buffer != NULL) {
	    	MessageX->Buffer->SelAttributes->Color = MessageX->PrintColor;
			// Display it in buffer
            MessageX->Buffer->SelStart = MessageX->Buffer->GetTextLen();
            MessageX->Buffer->SelText = String(MessageX->Line) + TEXT("\n");
			MessageX->Buffer->SelAttributes->Color = clBlack;				// return color to Black
            if (MessageX->bValue) { // keep cursor at bottom of buffer
                ScrollEnd(MessageX->Buffer);    	// remain at end of Buffer
                }
			}
        delete MessageX;	// delete the Message after processing it
		break;

    case M_SetSpinnerValue:		// needs to be tested
    	if (MessageX->Spinner != NULL) MessageX->Spinner->Value = MessageX->iValue;
        delete MessageX;	// delete the Message after processing it
        break;

    case M_GetSpinnerValue:
    	Application->ProcessMessages();		// make sure that VCL component TAbNumEdit is up to date (this worked!)
        if (MessageX->Spinner != NULL)
        	MessageX->iValue = MessageX->Spinner->Value;	// return value to Comm_Thread
            else
            MessageX->iValue = 0;
        // MessageX struct is deleted in Comm_Thread.cpp after the return value is read
    	break;

    case M_SetNumEditValue:
    	if (MessageX->Countr != NULL) MessageX->Countr->ValueAsInt = MessageX->iValue;
        delete MessageX;	// delete the Message after processing it
        break;

    case M_GetCntrValue:
    	Application->ProcessMessages();		// make sure that VCL component TAbNumEdit is up to date (this worked!)
        if (MessageX->Countr != NULL)
        	MessageX->Line = MessageX->Countr->Text;	// return text to Comm_Thread
            else
            MessageX->Line = "0";
        // MessageX struct is deleted in Comm_Thread.cpp after the return value is read
    	break;

    case M_SetPCLED:	// Set a LED to Visible & Checked, then set the color based on iState
    					// Line if not empty, sets the LED's Caption
                        // Used on the LED's on the Program Control window
    	if (MessageX->LED != NULL) {
            MessageX->LED->Visible = true;
            MessageX->LED->Checked = true;
            switch(MessageX->iState) {
            	case luaGreen:	// clGreen
                    MessageX->LED->LED->ColorOn = clLime;
                	break;
                case luaYellow:	// clYellow
                    MessageX->LED->LED->ColorOn = clYellow;
                	break;
                case luaClear:	// clClear makes the LED disappear
                	// clear
		            MessageX->LED->Visible = false;
        		    MessageX->LED->Checked = false;
                    break;
                case luaRed:	// clRed
                default:	// all other values
                    MessageX->LED->LED->ColorOn = clRed;
                	break;
                // clBlack, clBlue and clViolet are not valid and display as clRed
                }
            if (MessageX->Line != NULL && !MessageX->Line.IsEmpty()) {
            	MessageX->LED->Caption = MessageX->Line;
                }
        	}
        delete MessageX;	// delete the Message after processing it
        break;

    case M_SetNumEditColor:
    	if (MessageX->Countr != NULL) MessageX->Countr->ColorDefault = MessageX->ColorDefault;
        delete MessageX;	// delete the Message after processing it
        break;

    case M_GetComboBoxText:
    	Application->ProcessMessages();		// make sure that VCL component EditBox is up to date (this worked!)
        if (MessageX->ComboBox != NULL) {
            MessageX->Line = "";	// default return value
        	if (MessageX->iValue >= 0 && MessageX->iValue < MessageX->ComboBox->Items->Count) {
	        	MessageX->Line = MessageX->ComboBox->Items->Strings[MessageX->iValue];	// return text to Comm_Thread
                }
            }
        // MessageX struct is deleted in Comm_Thread.cpp after the return value is read
    	break;

    case M_ClearCntr:
		if (MessageX->Countr != NULL) MessageX->Countr->Value = 0;
        delete MessageX;	// delete the Message after processing it
 	    break;

    case M_SetCntr:
		if (MessageX->Countr != NULL) MessageX->Countr->Value = MessageX->fValue;
        delete MessageX;	// delete the Message after processing it
 	    break;

    case M_OpenAIUWindow:
    	// Open an AIU window
        {
        int iTIUNo = MessageX->iTIU;
        int iAIUNo = MessageX->iAIU;
        if (ptrAIUControl[iTIUNo-1][iAIUNo-1] == NULL) {
			ptrAIUControl[iTIUNo-1][iAIUNo-1] = new TAIUControl(this, RemoteNo, 1, iTIUNo, iAIUNo);   // create the Form & Pass "this" as the owner
			assert(ptrAIUControl[iTIUNo-1][iAIUNo-1] != NULL);	// make sure that construction worked
    		ptrAIUControl[iTIUNo-1][iAIUNo-1]->ParentHandle = MainForm->Handle; // for return messages
            // make the window visible since this was done by direct request
            ptrAIUControl[iTIUNo-1][iAIUNo-1]->Show();
            // Bring the PC window back to the front
            ptrAIUControl[iTIUNo-1][iAIUNo-1]->SendToBack();
            ptrProgram_Control[MessageX->PCNo-1]->SetFocus();
			} else {
            // make the window visible since this was done by direct request
            ptrAIUControl[iTIUNo-1][iAIUNo-1]->Show();
			}
        delete MessageX;	// delete the Message after processing it
        }
    	break;

    case M_GetAIUSwitchState:
        // fetch the state of the given TIU/AIU/Channel Switch
        {
        int iTIUNo = MessageX->iTIU;
        int iAIUNo = MessageX->iAIU;
        // Make sure the AIU window is open but not visible
        if (ptrAIUControl[iTIUNo-1][iAIUNo-1] == NULL) {
			ptrAIUControl[iTIUNo-1][iAIUNo-1] = new TAIUControl(this, RemoteNo, 1, iTIUNo, iAIUNo);   // create the Form & Pass "this" as the owner
			assert(ptrAIUControl[iTIUNo-1][iAIUNo-1] != NULL);	// make sure that construction worked
    		ptrAIUControl[iTIUNo-1][iAIUNo-1]->ParentHandle = MainForm->Handle; // for return messages
            // Bring the PC window back to the front by sending the new window to the back
            ptrAIUControl[iTIUNo-1][iAIUNo-1]->SendToBack();
            ptrProgram_Control[MessageX->PCNo-1]->SetFocus();
			}
        //
        int iChan = MessageX->iChan;
        if (ptrAIUControl[iTIUNo-1][iAIUNo-1] != NULL) {
	        MessageX->bValue = (ptrAIUControl[iTIUNo-1][iAIUNo-1]->AccData->Cells[SW_STATE][iChan] == "1") ? true : false;
            } else MessageX->bValue = false;
        }
        // MessageX struct is deleted in Comm_Thread.cpp after the return value is read
    	break;

    case M_GetAIUAccState:
        // fetch the state of the given TIU/AIU/Channel Accessory
        {
        int iTIUNo = MessageX->iTIU;
        int iAIUNo = MessageX->iAIU;
        // Make sure the AIU window is open but not visible
        if (ptrAIUControl[iTIUNo-1][iAIUNo-1] == NULL) {
			ptrAIUControl[iTIUNo-1][iAIUNo-1] = new TAIUControl(this, RemoteNo, 1, iTIUNo, iAIUNo);   // create the Form & Pass "this" as the owner
			assert(ptrAIUControl[iTIUNo-1][iAIUNo-1] != NULL);	// make sure that construction worked
    		ptrAIUControl[iTIUNo-1][iAIUNo-1]->ParentHandle = MainForm->Handle; // for return messages
            // Bring the PC window back to the front by sending the new window to the back
            ptrAIUControl[iTIUNo-1][iAIUNo-1]->SendToBack();
            ptrProgram_Control[MessageX->PCNo-1]->SetFocus();
			}
        //
        int iChan = MessageX->iChan;
        if (ptrAIUControl[iTIUNo-1][iAIUNo-1] != NULL) {
	        MessageX->bValue = (ptrAIUControl[iTIUNo-1][iAIUNo-1]->AccData->Cells[ACC_STATE][iChan] == "1") ? true : false;
            } else MessageX->bValue = false;
        }
        // MessageX struct is deleted in Comm_Thread.cpp after the return value is read
    	break;

    case M_GetAIUSwitchName:
        // fetch the name of the given TIU/AIU/Channel Switch
        {
        int iTIUNo = MessageX->iTIU;
        int iAIUNo = MessageX->iAIU;
        // Make sure the AIU window is open but not visible
        if (ptrAIUControl[iTIUNo-1][iAIUNo-1] == NULL) {
			ptrAIUControl[iTIUNo-1][iAIUNo-1] = new TAIUControl(this, RemoteNo, 1, iTIUNo, iAIUNo);   // create the Form & Pass "this" as the owner
			assert(ptrAIUControl[iTIUNo-1][iAIUNo-1] != NULL);	// make sure that construction worked
    		ptrAIUControl[iTIUNo-1][iAIUNo-1]->ParentHandle = MainForm->Handle; // for return messages
            // Bring the PC window back to the front by sending the new window to the back
            ptrAIUControl[iTIUNo-1][iAIUNo-1]->SendToBack();
            ptrProgram_Control[MessageX->PCNo-1]->SetFocus();
			}
        //
        int iChan = MessageX->iChan;
        if (ptrAIUControl[iTIUNo-1][iAIUNo-1] != NULL) {
	        MessageX->Line = ptrAIUControl[iTIUNo-1][iAIUNo-1]->AccData->Cells[SW_NAME][iChan];
            } else MessageX->Line = "*";
        }
        // MessageX struct is deleted in Comm_Thread.cpp after the return value is read
    	break;

    case M_GetAIUAccName:
        // fetch the name of the given TIU/AIU/Channel Accessory
        {
        int iTIUNo = MessageX->iTIU;
        int iAIUNo = MessageX->iAIU;
        // Make sure the AIU window is open but not visible
        if (ptrAIUControl[iTIUNo-1][iAIUNo-1] == NULL) {
			ptrAIUControl[iTIUNo-1][iAIUNo-1] = new TAIUControl(this, RemoteNo, 1, iTIUNo, iAIUNo);   // create the Form & Pass "this" as the owner
			assert(ptrAIUControl[iTIUNo-1][iAIUNo-1] != NULL);	// make sure that construction worked
    		ptrAIUControl[iTIUNo-1][iAIUNo-1]->ParentHandle = MainForm->Handle; // for return messages
            // Bring the PC window back to the front by sending the new window to the back
            ptrAIUControl[iTIUNo-1][iAIUNo-1]->SendToBack();
            ptrProgram_Control[MessageX->PCNo-1]->SetFocus();
			}
        //
        int iChan = MessageX->iChan;
        if (ptrAIUControl[iTIUNo-1][iAIUNo-1] != NULL) {
	        MessageX->Line = ptrAIUControl[iTIUNo-1][iAIUNo-1]->AccData->Cells[ACC_NAME][iChan];
            } else MessageX->Line = "*";
        }
        // MessageX struct is deleted in Comm_Thread.cpp after the return value is read
    	break;

    case M_MakeItSo:
    	// set all accessories and switches per their AIU states
        {
        int iTIUNo = MessageX->iTIU;
        int iAIUNo = MessageX->iAIU;
        // Make sure the AIU window is open but not visible
        if (ptrAIUControl[iTIUNo-1][iAIUNo-1] == NULL) {
			ptrAIUControl[iTIUNo-1][iAIUNo-1] = new TAIUControl(this, RemoteNo, 1, iTIUNo, iAIUNo);   // create the Form & Pass "this" as the owner
			assert(ptrAIUControl[iTIUNo-1][iAIUNo-1] != NULL);	// make sure that construction worked
    		ptrAIUControl[iTIUNo-1][iAIUNo-1]->ParentHandle = MainForm->Handle; // for return messages
            // Bring the PC window back to the front by sending the new window to the back
            ptrAIUControl[iTIUNo-1][iAIUNo-1]->SendToBack();
            ptrProgram_Control[MessageX->PCNo-1]->SetFocus();
			}
        // press the MakeItSo button on the selected AIU
        ptrAIUControl[iTIUNo-1][iAIUNo-1]->MakeItSoButtonClick(NULL);
        }
        delete MessageX;	// delete the Message after processing it
		break;

    case M_SetVirtualAIUAccState:
        // TODO : Needs to be tested - set the state of the given TIU/AIU/Channel - Accessory channels only
        {
        int iTIUNo = MessageX->iTIU;
        int iAIUNo = MessageX->iAIU;
		int iChan = MessageX->iChan;
		bool bState = MessageX->bValue;
        // Make sure the AIU window is open but not visible
        if (ptrAIUControl[iTIUNo-1][iAIUNo-1] == NULL) {
			ptrAIUControl[iTIUNo-1][iAIUNo-1] = new TAIUControl(this, RemoteNo, 1, iTIUNo, iAIUNo);   // create the Form & Pass "this" as the owner
			assert(ptrAIUControl[iTIUNo-1][iAIUNo-1] != NULL);	// make sure that construction worked
    		ptrAIUControl[iTIUNo-1][iAIUNo-1]->ParentHandle = MainForm->Handle; // for return messages
            // Bring the PC window back to the front by sending the new window to the back
            ptrAIUControl[iTIUNo-1][iAIUNo-1]->SendToBack();
            ptrProgram_Control[MessageX->PCNo-1]->SetFocus();
			}
        // set the accessory status of any AIU from 1 to AIUMAX - can be virtual
        ptrAIUControl[iTIUNo-1][iAIUNo-1]->AccData->Cells[ACC_STATE][iChan] = (bState ? "1" : "0");
        }
        delete MessageX;	// delete the Message after processing it
		break;

    case M_GetStringGridText:
		// return Engine name and PS LEvel
    	Application->ProcessMessages();		// make sure that VCL component StringList is up to date (this worked!)
        if (MessageX->StringGrid != NULL) {
        	MessageX->Line = MessageX->StringGrid->Cells[ENG_NAME][MessageX->iValue];
            MessageX->iValue =  MessageX->StringGrid->Cells[ENG_PS][MessageX->iValue].ToInt();;
            }
        // reurns engine name and engine PSx value
        // MessageX struct is deleted in Comm_Thread.cpp after the return value is read
    	break;

    case M_SetLabelText:
    	if (MessageX->Label != NULL) MessageX->Label->Caption = MessageX->Line;
        delete MessageX;	// delete the Message after processing it
    	break;

    case M_PopupMessage:
		// display a popup message window
        // Keep it on the screen for iValue milliseconds
		// no buttons to push
		// Start Thread
		// FreeOnTerminate is set in constructor so we don't need to worry about freeing memory
		new TMessageDisplayThread(false, this, Handle, MessageX->Caption, MessageX->Line, MessageX->Status, MessageX->iValue);
        delete MessageX;	// delete the Message after processing it
    	break;

   	// Flags -------------------------------------------------------------------
	case M_GetFlag:
    	// Get a integer flag value from flag's "Tag" property
        // Requires a Layout window be shown, call ShowLayout() if needed
		if (LayoutForm) {
            MessageX->iState = LayoutForm->GetFlag(MessageX->iValue);
            } else {
            MessageX->iState = UNOCCUPIED;   // simulates that the block is empty
            }
        // returns the interger flag value
        // MessageX struct is deleted in Comm_Thread.cpp after the return value is read
    	break;

    case M_SetFlag:
    	// Set a integer flag value into flag's "Tag" property and
        // Requires a Layout window be shown, call ShowLayout() if needed
		if (LayoutForm) {
            MessageX->iState = LayoutForm->SetFlag(MessageX->iValue, MessageX->iState);
            } else {
            MessageX->iState = UNOCCUPIED;   // simulates that the block is empty
            }
        // returns the integer flag value
        // MessageX struct is deleted in Comm_Thread.cpp after the return value is read
		break;

    case M_GetFlagName:
    	// Get a flag name from flag's "Caption" property
        // Requires a Layout window be shown, call ShowLayout() if needed
		if (LayoutForm) {
            MessageX->Line = LayoutForm->GetFlagName(MessageX->iValue);
            } else {
            MessageX->Line = "Flag #" + String(MessageX->iValue);
            }
        // returns flag name
        // MessageX struct is deleted in Comm_Thread.cpp after the return value is read
    	break;

    case M_SetFlagName:
    	// Set a flag name into flag's "Caption" property
        // Requires a Layout window be shown, call ShowLayout() if needed
		if (LayoutForm) {
            MessageX->iState = LayoutForm->SetFlagName(MessageX->iValue, MessageX->Line);
            } else {
            MessageX->iState =  0;   // simulates that the block is empty
            }
        // returns flag value
        // MessageX struct is deleted in Comm_Thread.cpp after the return value is read
    	break;

	// Fixed Block Signals -----------------------------------------------------
	case M_GetAspect:
    	// Get the Aspect value, the number of lights on the signal head and the signal type
        // Requires a Layout window be shown, call ShowLayout() if needed
		if (LayoutForm) {
            MessageX->iAspect = LayoutForm->GetAspect(MessageX->iSignalNo, MessageX->iMode, MessageX->iType);
            } else {
            MessageX->iAspect =  0;   // simulates that the block is empty
            }
        // returns signal aspect
        // MessageX struct is deleted in Comm_Thread.cpp after the return value is read
    	break;

    case M_SetAspect:
    	// Set a Aspect value into Block signal's "Tag" and "Aspect" properties
        // Requires a Layout window be shown, call ShowLayout() if needed
		if (LayoutForm) {
            MessageX->iAspect = LayoutForm->SetAspect(MessageX->iSignalNo, MessageX->iAspect);
            }
        // returns signal aspect
        // MessageX struct is deleted in Comm_Thread.cpp after the return value is read
		break;

    case M_GetSignalName:
    	// Get a Block's signal name from Signal's "Caption" property, the value on
        // the numberplate and the Signal's Type
        // Requires a Layout window be shown, call ShowLayout() if needed
		if (LayoutForm) {
            MessageX->Line = LayoutForm->GetSignalName(MessageX->iSignalNo, MessageX->iNumberPlate, MessageX->iType);
            } else {
            MessageX->Line = "Signal #"+ String(MessageX->iSignalNo);
            }
		// returns signal name, number plate and signal type
        // MessageX struct is deleted in Comm_Thread.cpp after the return value is read
    	break;

    case M_CreateSignal:
    	// Create a Block signal
        // Requires a Layout window be shown, call ShowLayout() if needed
		if (LayoutForm) {
            MessageX->iAspect = LayoutForm->CreateSignal(MessageX->iSignalNo, MessageX->iDir, MessageX->iType, MessageX->iCol,
        	    MessageX->iRow, /* SignalName */ MessageX->Line, MessageX->iMode, MessageX->iAspect, MessageX->iNumberPlate);
            }
        // returns signal aspect
        // MessageX struct is deleted in Comm_Thread.cpp after the return value is read
    	break;

    case M_ShowLayout:
        // create and show the Layout window if not already done
		if (!LayoutForm) {
            ShowLayoutWindowClick(NULL);
            }
        delete MessageX;	// delete the Message after processing it
    	break;

	case M_DeleteAllSignals:
        // Requires a Layout window be shown, call ShowLayout() if needed
		if (LayoutForm) {
            LayoutForm->DeleteAllSignals();
            }
		delete MessageX;	// delete the Message after processing it
		break;

	// Cab Signals -------------------------------------------------------------
    case M_SetCabAspect:
        ptrProgram_Control[MessageX->PCNo-1]->SetCabSignal(MessageX->iSignalNo, MessageX->iAspect, "", -1, -1);
    	delete MessageX;	// delete the Message after processing it
        break;

    case M_SetCabLabels:
        ptrProgram_Control[MessageX->PCNo-1]->SetCabSignal(MessageX->iSignalNo, MessageX->iAspect, MessageX->Line, MessageX->iNumberPlate,
        	MessageX->iType);
    	delete MessageX;	// delete the Message after processing it
        break;

    case M_CreateCab:
        ptrProgram_Control[MessageX->PCNo-1]->CreateCabSignals(MessageX->iSignalNo, MessageX->iDistant);
    	delete MessageX;	// delete the Message after processing it
        break;

    case M_GetCabAspect:	// and Type
        ptrProgram_Control[MessageX->PCNo-1]->GetCabAspect(MessageX->iSignalNo, MessageX->iAspect, MessageX->iMode, MessageX->iType);
		// returns signal aspect, number of lights on signal head and signal type
    	// MessageX struct is deleted in Comm_Thread.cpp after the return value is read
        break;

    case M_SetLED:		// Set LED's On Color from on iState
                        // Set LED's Checked from iValue
    					// Set LED's Flashing from iTYpe
    					// Line, if not empty, sets the LED's Caption
    	// Used on the LED's on the Main window (ConnectedRadioLED, ConnectedRFIDLED, and ConnectedTIULED)
    	if (MessageX->LED != NULL) {
            MessageX->LED->LED->ColorOn = (TColor) MessageX->iState;
            MessageX->LED->Checked = MessageX->iValue;
            MessageX->LED->Flashing = MessageX->iType;
            if (MessageX->Line != NULL && !MessageX->Line.IsEmpty()) {
            	MessageX->LED->Caption = MessageX->Line;
                }
        	}
        delete MessageX;	// delete the Message after processing it
        break;

    case M_AddCntr:
        if (MessageX->Countr != NULL) MessageX->Countr->Value += MessageX->iValue;
        delete MessageX;	// delete the Message after processing it
        break;

    case M_GetListBoxText:
		// return selected string from List Box
    	Application->ProcessMessages();		// make sure that VCL component ListBox is up to date (this worked!)
        if (MessageX->ListBox != NULL) {
            if (MessageX->ListBox->Items->Count > MessageX->iValue) {
            	MessageX->Line = MessageX->ListBox->Items->Strings[MessageX->iValue];
                } else {
                MessageX->Line = "";
                }
            }
        // returns selected string from list Box or empty string if iValue is out of range
        // MessageX struct is deleted in Comm_Thread.cpp after the return value is read
        break;

    case M_GetTIUNo:
        // return the TIU that the Engine in iEngineNo is associated with
        // TIU number will be from 0 to 5
        MessageX->iTIU = EngineData->Cells [TIU_NUMBER] [MessageX->iEngineNo].ToIntDef(0);
    	// MessageX struct is deleted in Comm_Thread.cpp after the return value is read
        break;

    case M_RockerSwitch:
        if (MessageX->RockerSwitch != NULL) MessageX->RockerSwitch->Checked = MessageX->bValue;
        delete MessageX;	// delete the Message after processing it
        break;

    case M_SetButtonCaption:
    	if (MessageX->But != NULL) MessageX->But->Caption = MessageX->Line;
        delete MessageX;	// delete the Message after processing it
    	break;

    case M_RouteScene:
        switch (MessageX->iValue) {
            case RSACTIVATE:
                // Activate Route/Scene given by iRSNo
                RouteScene->RSData->Row = MessageX->iRSNo;
                RouteScene->RSActivateButtonClick(NULL);
                break;
            case RSRESET:
                // Reset Route/Scene given by iRSNo
                RouteScene->RSData->Row = MessageX->iRSNo;
                RouteScene->RSResetButtonClick(NULL);
                break;
            case SETALLSWNORMAL:
                // set all switches normal for Route/Scene given by iRSNo
                RouteScene->RSData->Row = MessageX->iRSNo;
                RouteScene->AllSwNormalButtonClick(NULL);
                break;
            case SETALLACCYON:
                // set all accessories on for Route/Scene given by iRSNo
                RouteScene->RSData->Row = MessageX->iRSNo;
                RouteScene->AllAccyOnButtonClick(NULL);
                break;
            case SETALLACCYOFF:
                // set all accessories off for Route/Scene given by iRSNo
                RouteScene->RSData->Row = MessageX->iRSNo;
                RouteScene->AllAccyOffButtonClick(NULL);
                break;
            }
        delete MessageX;	// delete the Message after processing it
    	break;

    default:
	    if (DEBUGGING) {
        	DB->Debug_Print("MainForm::CMUI Unknown User Interface Message", clRed);
            } else {
            assert(false);
            }
        delete MessageX;	// delete the message
		break;
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CMProcessRFIDData(TMessage &Message)
{
// process ASCII Character message received from Tag Reader port - CM_PROCESSRFIDDATA
// By putting the message into the RFIDFIFO. Then send RFIDReqEvent to get
// tag processed by PC_Thread.cpp. Tags are sent to all instances of PC_Thread.
// Handles both Serial Tag Detectors (called from Comm_Thread) and WiFi Tag Detectors (called from MainForm)
//
// Note: Message.LParam points to a memory block of 57 bytes to hold the longest
// possible tag packet
//
// Message.LParam created by "new". Need to free() it in this routine
//
char * sTagBuf = (char *) Message.LParam;	// set address of buffer to process from Long Parameter
assert (sTagBuf != NULL);
int length = strlen(sTagBuf);
String TagLine;

if (DEBUGGING) {
	TagLine = String((char *)sTagBuf);
	if (DEBUGGINGX(4)) DB->Debug_Print("Tag Reader Message " + TagLine + " (" + String(length) + " bytes)", clBlue);
	if (RFIDLog->Checked) {
		// display it in the RFID buffer
		RFIDBuffer->SelAttributes->Color = clGreen;
		RFIDBuffer->SelStart = RFIDBuffer->GetTextLen();
		RFIDBuffer->SelText = TagLine + TEXT("\r\n");    // CR LF
        ScrollEnd(RFIDBuffer);    	// remain at end of Buffer
		}
	}
Tags->Value++;				// increment totals number of packets received (includes tags and startup packets)
//
/*
Note: on TCP connection to detectors, 3 packets are received from each detector and 1 packet is received for each reader.
//
FF05333401010270F0 (18 bytes)  HELLOPACKET
FF05000007104465746563746F722D3520202020202078F0 (48 bytes) DETECTORNAME
FF05010008103139322E3136382E312E31323120202008F0 (48 bytes) DETECTORIP
FF050000040009F0 (16 bytes)  PN53xFOUND for each attached tag reader
FF05010004000AF0 (16 bytes)  PN53xFOUND
//
  full tag packet tags are 56 bytes long
FF0500002114864C134A88080101030108011B0220203135363132F0
  UID only tag packet (0x03) is 24 bytes long
FF0500030304864C134A3EF0
*/
// TODO : Check the Checksum at some point, but error rate is very low
//
// add to detected data tags list unless it is already there
if (length == 56) {
	DetectedRFIDTags->Add(String(sTagBuf).SubString(13, 40));
    }
// add to UID detected data tags list unless it is already there
if (length == 24) {
	DetectedRFIDTags->AddUID(String(sTagBuf).SubString(13, 8));
    }
unsigned char PacketType = Hex2Digit_to_Decimal(&sTagBuf[PACKETTYPEIDX]);
switch (PacketType) {

    // if it is a UID only tag, look it up in the TagsDetectedList
	case TAGDETECTED:
        {
        assert(length == 24);
        // keep count of UID only tags
        DetectedRFIDTags->UIDTagCount->Value++;
		// Look up the UID
		// NewPacket must be AnsiString because the c_str() taken needs to be a char type
        AnsiString NewPacket = DetectedRFIDTags->LookUpTagPacket(String(sTagBuf).SubString(13, 8));
        if (!NewPacket.IsEmpty()) {
            // if we get a good tag lookup, use that looked up tag
            // NewPacket is 40 characters, 8 bytes of UID and 32 bytes of Enginedata
            // TODO : Should the Checksum be calculated rather than just set to zero?
            NewPacket = String(sTagBuf).SubString(1, 8) + "2114" + NewPacket + "00F0"; // command 0x21 and byte count 0x14 = 20 (4 bytes UID & 8 bytes Engine Data)
            length = NewPacket.Length();
            assert(length == 56);
            strncpy(sTagBuf, NewPacket.c_str(), NewPacket.Length());
            //
            if (DEBUGGINGX(2)) {
                String PrintLine;
                PrintLine = "Detector " + String(Hex2Digit_to_Decimal(&sTagBuf[DETECTORIDX])) +
                            " Reader " + String(Hex2Digit_to_Decimal(&sTagBuf[READERIDX])) + " : (" + String(Tags->Value) + ") Substituting Looked Up Packet " + NewPacket;
                DB->Debug_Print(PrintLine, clRed);
                }
            }
        // Global Counter to keep track of Tags Substituted
        DetectedRFIDTags->TagSubCount->Value++;
        DetectedRFIDTags->TagSubCount->ColorDefault = clRed;
        // process the saved tag packet as if it were just received
        break;
        }

    // Check if its an HELLOPACKET and display a message in the Debug Message Window
    case HELLOPACKET:
        if (DEBUGGINGX(2)) {
            UP Packet;
            // to display this, we have to convert the packet to binary
            for (int iDex = 0 ; iDex < (48/2); iDex++) {
                Packet.Byte_Array[iDex] = Hex2Digit_to_Decimal(&sTagBuf[iDex * 2]);
                }
			String PrintLine = "Detector " + String(Packet.Detector_Name_Packet.Header.Detector) + " : (" + String(Tags->Value) + ") Hello";
            DB->Debug_Print(PrintLine, clBlue);
			}
        break;

    // Check if it is a PN53xFOUND packet and display a message in the Debug Message Window
    case PN53xFOUND:
        if (DEBUGGINGX(4)) {
            UP Packet;
            // to display this, we have to convert the packet to binary
            for (int iDex = 0 ; iDex < (48/2); iDex++) {
                Packet.Byte_Array[iDex] = Hex2Digit_to_Decimal(&sTagBuf[iDex * 2]);
                }
			String PrintLine = "Detector " + String(Packet.Detector_Name_Packet.Header.Detector) + " : (" + String(Tags->Value) + ") PN532 Reader Found";
            DB->Debug_Print(PrintLine, clBlue);
            }
        break;

    // Check if its is a DETECTORNAME packet and display the name in the Debug Message Window
    case DETECTORNAME:
        // Bump the number of Detectors
        //NumDetectors->Value++;    This value set in Setup
        if (DEBUGGINGX(4)) {
            UP Packet;
            char cmsgBuf[17];
            // to display this, we have to convert the packet to binary
            for (int iDex = 0 ; iDex < (48/2); iDex++) {
                Packet.Byte_Array[iDex] = Hex2Digit_to_Decimal(&sTagBuf[iDex * 2]);
                }
            memcpy(cmsgBuf, &Packet.Detector_Name_Packet.Detector_Name, 16);
            cmsgBuf[16] = '\0';	// Null Terminate
			{	// remove trailing spaces
                int idx = 15;
                while(1) {
                    if (idx == -1) break;	// done
                    if (cmsgBuf[idx] == ' ') {	// is the character a space?
                        cmsgBuf[idx] = '\0';	// yes
                        idx--;
                        continue;
                        }
                    break;	// first non-space and we are done
                    }
			}
			String PrintLine = "Detector " + String(Packet.Detector_Name_Packet.Header.Detector) + " : (" + String(Tags->Value) + ") Name " + String(cmsgBuf);
            DB->Debug_Print(PrintLine, clBlue);
            }
        break;

    // Check if its is a DETECTORIP packet and display the IP Address in the Debug Message Window
    case DETECTORIP:
        if (DEBUGGINGX(4)) {
            UP Packet;
            char cmsgBuf[17];
            // to display this, we have to convert the packet to binary
            for (int iDex = 0 ; iDex < (48/2); iDex++) {
                Packet.Byte_Array[iDex] = Hex2Digit_to_Decimal(&sTagBuf[iDex * 2]);
                }
            memcpy(cmsgBuf, &Packet.Detector_Name_Packet.Detector_Name, 16);
            cmsgBuf[16] = '\0';	// Null Terminate
			{	// remove trailing spaces
                int idx = 15;
                while(1) {
                    if (idx == -1) break;	// done
                    if (cmsgBuf[idx] == ' ') {	// is the character a space?
                        cmsgBuf[idx] = '\0';	// yes
                        idx--;
                        continue;
                        }
                    break;	// first non-space and we are done
                    }
            }
            String PrintLine = "Detector " + String(Packet.Detector_Name_Packet.Header.Detector) + " : (" + String(Tags->Value) + ") IP " + String(cmsgBuf) +
                " using " + (Packet.Data_Packet.Header.Reader == 0 ? "UDP" : "TCP");
            DB->Debug_Print(PrintLine, clBlue);
            }
        break;
    case NOP:
        if (DEBUGGINGX(6)) {
            UP Packet;
            // to display this, we have to convert the packet to binary
            for (int iDex = 0 ; iDex < (48/2); iDex++) {
                Packet.Byte_Array[iDex] = Hex2Digit_to_Decimal(&sTagBuf[iDex * 2]);
                }
			String PrintLine = "Detector " + String(Packet.Detector_Name_Packet.Header.Detector) + " : (" + String(Tags->Value) + ") " + TagLine + " (" + String(length) + " bytes)";
            DB->Debug_Print(PrintLine, clBlue);
            }
        break;
        
    default:
        if (DEBUGGINGX(2)) {
            UP Packet;
            // to display this, we have to convert the packet to binary
            for (int iDex = 0 ; iDex < (48/2); iDex++) {
                Packet.Byte_Array[iDex] = Hex2Digit_to_Decimal(&sTagBuf[iDex * 2]);
                }
			String PrintLine = "Detector " + String(Packet.Detector_Name_Packet.Header.Detector) + " : (" + String(Tags->Value) + ") " + TagLine + " (" + String(length) + " bytes)";
            DB->Debug_Print(PrintLine, clBlue);
            }
        break;
    }
// Count all responses from the Detectors
DetectedRFIDTags->RespCount->Value++;
// add to all of the RFID FIFO's
//
// send the packet to every PC thread that is is active and contains a tag(), nametag() or uidtag() function
// each PC thread has its own FIFO. Individual RFIDReqEvent
// are created by each PC thread. We send that even to each thread. The PC thread
// should ignore any packet that it does care about
//
// Note that each packet has a checksum and an end of packet character (0xF0) appended
//
for (int iPCNo = 1 ; iPCNo <= PCMAX ; iPCNo++) {
    if (ptrProgram_Control[iPCNo - 1] == NULL) continue;							// PC Window must be running, this one isn't, try next one
    if (ptrProgram_Control[iPCNo - 1]->ptrPC_Thread == NULL) continue;				// PC Thread must be running, this one isn't, try next one
    if (ptrProgram_Control[iPCNo - 1]->ptrPC_Thread->RFIDFIFO == NULL) continue;	// RFIDFIFO must be created, this one isn't, try next one
    // make a copy of the tag
    char * sNewBuf = (char *)malloc(EOTIDX + 2 + 1);	// need to allocate at least 57 bytes (56 for the packet and 1 for null);
    memcpy(sNewBuf, sTagBuf, EOTIDX + 2 + 1);

    TList *myList = ptrProgram_Control[iPCNo - 1]->ptrPC_Thread->RFIDFIFO->LockList();	// lock the RFIDFIFO
    assert(myList);
    ptrProgram_Control[iPCNo - 1]->ptrPC_Thread->RFIDFIFO->Add(sNewBuf);
    ptrProgram_Control[iPCNo - 1]->ptrPC_Thread->RFIDFIFO->UnlockList();				// unlock the RFIDFIFO
	// Signal the RFID Request event to all PC threads
	SetEvent(hRFIDReqEvent[iPCNo - 1]);
    }
// each PC thread in RFIDTagHandler() will delete[] the copy of the tag at sNewBuf[] pointed to by the RFIDFIFO.
// We delete the original here that was passed in Message.LParam.
delete [] sTagBuf;
return;
}
//--------------------------------------------------------------------------
//
// NOT USED - performed in Comm_Thread.cpp
//
void __fastcall TMainForm::CMProcessRadioData(TMessage &Message)
{
// process ASCII Character message received from Radio port - CM_PROCESSRADIODATA
//
assert(false);		// never called
//
}
//--------------------------------------------------------------------------
//
// NOT USED - all sending is done in Comm_Thread which can call RadioSend directly
//
void __fastcall TMainForm::CMSendWiFiPacket(TMessage &Message)
{
// Send a packet to the Radio via WiFi - CM_SENDWIFIPACKET
//
assert(false);		// never called - moved to TCommThread::Execute()
//
char * sSendBuf = (char *) Message.LParam;	// set address of buffer to process from Long Parameter

delete sSendBuf;
}
//--------------------------------------------------------------------------

void __fastcall TMainForm::Channel1Click(TObject *Sender)
{
// Activate Channel 1
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
// if selected TIU is not connected fail
if (!TIUSelection[TIUSpin->ValueAsInt - 1]) {
	DebugMessagePopup("Warning", MainForm->TIUActiveMsg, "");
	return;
    }
// open Var control
StartUpVarControl(1);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::Channel2Click(TObject *Sender)
{
// Activate Channel 2
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
// if selected TIU is not connected fail
if (!TIUSelection[TIUSpin->ValueAsInt - 1]) {
	DebugMessagePopup("Warning", MainForm->TIUActiveMsg, "");
	return;
    }
// open Var control
StartUpVarControl(2);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::EngineStatus1Click(TObject *Sender)
{
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }

// Engine Status popup menu item
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
assert(EngineNo >= (Setup->AllowZero->Checked? 0 : 1) && EngineNo <= ENGINEMAX);	// 0/1-99
//
// Manually create form and show it (menu item is disabled for LashUps and All Engine Operation)
// if the form is not already created, then create it
// ------------ With programmed create using C++ Builder's generated pointer
if (ptrEngineStatus[EngineNo] == NULL) {	// Engine 0-99 (0 is factory reset, 1-99)
    int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
    if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
        MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
        return;
        }
	ptrEngineStatus[EngineNo] = new TEngineStatus(this, Handle, RemoteNo, EngineNo, MyTIUNo, EngineData->Cells[ENG_NAME][EngineNo]);   // create the Form and show it
								// Pass "this" as the owner
    							// 1-99 Engine Number
	assert(ptrEngineStatus[EngineNo] != NULL);	// make sure that construction worked
	} else {    // it's already created
	ptrEngineStatus[EngineNo]->Show();
	}
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::EngineTrackSignal1Click(TObject *Sender)
{
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }

// Engine Status popup menu item
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
assert(EngineNo >= (Setup->AllowZero->Checked? 0 : 1) && EngineNo <= ENGINEMAX);	// 0/1-99
//
// Manually create form and show it (menu item is disabled for LashUps and All Engine Operation)
// if the form is not already created, then create it
// ------------ With programmed create using C++ Builder's generated pointer
if (ptrTrackSignal[EngineNo] == NULL) {	// Engine 0-99 (0 is factory reset, 1-99)
    int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
    if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
        MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
        return;
        }
	ptrTrackSignal[EngineNo] = new TTrackSignal(this, Handle, RemoteNo, EngineNo, MyTIUNo, EngineData->Cells[ENG_NAME][EngineNo]);   // create the Form and show it
								// Pass "this" as the owner
    							// 1-99 Engine Number
	assert(ptrTrackSignal[EngineNo] != NULL);	// make sure that construction worked
	} else {    // it's already created
	ptrTrackSignal[EngineNo]->Show();
	}
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::EngineInformation1Click(TObject *Sender)
{
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }

// Engine Data popup menu item
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
assert(EngineNo >= (Setup->AllowZero->Checked? 0 : 1) && EngineNo <= ENGINEMAX);	// 0/1-99
//
// Manually create form and show it (menu item is disabled for LashUps and All Engine Operation)
// if the form is not already created, then create it
// ------------ With programmed create using C++ Builder's generated pointer
if (ptrEngineInfo[EngineNo] == NULL) {	// Engine 0-99 (0 is factory reset, 1-99)
    int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
    if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
        MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
        return;
        }
	ptrEngineInfo[EngineNo] = new TEngineInfo(this, Handle, RemoteNo, EngineNo, MyTIUNo, EngineData->Cells[ENG_NAME][EngineNo]);   // create the Form and show it
								// Pass "this" as the owner
    							// 1-99 Engine Number
	assert(ptrEngineInfo[EngineNo] != NULL);	// make sure that construction worked
	} else {    // it's already created
	ptrEngineInfo[EngineNo]->Show();
	}
}
//---------------------------------------------------------------------------
bool __fastcall TMainForm::FetchByte(unsigned int addr, byte * value, byte Eng, byte TIU)
{
//
// Fetch one byte of RAM memory
// addr points to the byte address
// byte result in 1 byte
// Never addressed to a Lashup
// Returns false on error
//
assert(addr <= 0x03FF);
*value = 0;
if (Eng == 0 || Eng >= ALLENGINE) return false;
byte FBResp[CMDMAX];
char CommandString[15];
int iTIUCount;
snprintf(CommandString, sizeof CommandString, "V1%03X", addr);
bool bretv = ptrTIUCommThread->Send_String_FIFO(Eng, CommandString, TIU, RemoteNo, 21, QUERY_WAIT, NO_RECORD, NULL, NULL, &iTIUCount, FBResp);
if (!bretv) {	// retry once
	if (DEBUGGING) DB->Debug_Print("FetchByte() Retrying " + String(CommandString));
	bretv = ptrTIUCommThread->Send_String_FIFO(Eng, CommandString, TIU, RemoteNo, 21, QUERY_WAIT, NO_RECORD, NULL, NULL, &iTIUCount, FBResp);
	if (!bretv) return false;	// return false with value == 0 if we can't read RAM
    }

if (bretv) {
    if (Setup->Radio_COM_Mode->Checked) {
        // Handle Radio Response
        // pull out the value         0    1    2    3
        // 965A 99A6 5555 5555 5559 5559 A596 6566 555A
        //   87   D9   00   00   04   04   E1   31   05
        //                            ^^    RAM -- returned by the TIU
        *value =  FBResp[5];
        }
    if (Setup->WTIU_WiFi_Mode->Checked) {
        // Handle WTIU ASCII Response  "qE8 00 36 0A  okay"
        //                               ^^     RAM -- returned by the TIU
        int h0;
		int retv = sscanf(&FBResp[1], "%x", &h0);   // %x requires &int
        if (retv == 1) {
            *value = h0;
            } else {
            return false;
            }
        }
	}

if (DEBUGGING) DB->Debug_Print("Fetch 8 bit Byte     - address : 0x" + IntToHex((int)addr, 3) + " value : 0x" + IntToHex((int)*value, 2));
return true;
}
//---------------------------------------------------------------------------
bool __fastcall TMainForm::FetchWord(unsigned int addr, unsigned short * value, byte Eng, byte TIU)
{
//
// Fetch two bytes of RAM memory
// addr points to the lowest byte address
// unsigned short result in 2 bytes
// Never addressed to a Lashup
// Returns false on error
//
assert(addr <= 0x03FF);
*value = 0;
if (Eng == 0 || Eng >= ALLENGINE) return false;
byte FBResp[CMDMAX];
char CommandString[15];
int iTIUCount;
snprintf(CommandString, sizeof CommandString, "V2%03X", addr);
bool bretv = ptrTIUCommThread->Send_String_FIFO(Eng, CommandString, TIU, RemoteNo, 21, QUERY_WAIT, NO_RECORD, NULL, NULL, &iTIUCount, FBResp);
if (!bretv) {	// retry once
	if (DEBUGGING) DB->Debug_Print("FetchWord() Retrying " + String(CommandString));
	bretv = ptrTIUCommThread->Send_String_FIFO(Eng, CommandString, TIU, RemoteNo, 21, QUERY_WAIT, NO_RECORD, NULL, NULL, &iTIUCount, FBResp);
	if (!bretv) return false;	// return false with value == 0 if we can't read RAM
	}
if (bretv) {
	if (Setup->Radio_COM_Mode->Checked) {
		// Handle Radio Response
		// pull out the value         0    1    2    3
		// 965A 99A6 5555 5555 5559 5559 A596 6566 555A
		//   87   D9   00   00   04   04   E1   31   05
		//                            ^^   ^^  RAM -- returned by the TIU
		*value =  FBResp[5] << 8 | FBResp[6];
		}
	if (Setup->WTIU_WiFi_Mode->Checked) {
		// Handle WTIU ASCII Response  "qE8 00 36 0A  okay"
		//                               ^^ ^^     RAM -- returned by the TIU
		int h0, h1;
		int retv = sscanf(&FBResp[1], "%x %x", &h0, &h1);   // %x requires &int
		if (retv == 2) {
			*value = h0 << 8 | h1;
			} else {
			return false;
			}
		}
	}

if (DEBUGGING) DB->Debug_Print("Fetch 16 bit Word    - address : 0x" + IntToHex((int)addr, 3) + " value : 0x" + IntToHex((int)*value, 4));
return true;
}
//---------------------------------------------------------------------------
bool __fastcall TMainForm::FetchInt(unsigned int addr, unsigned long * value, byte Eng, byte TIU)
{
//
// Fetch four bytes of RAM memory
// addr points to the lowest byte address
// unsigned long result in 4 bytes
// Never addressed to a Lashup
// Returns false on error
//
assert(addr <= 0x03FF);
*value = 0;
if (Eng == 0 || Eng >= ALLENGINE) return false;
byte FBResp[CMDMAX];
char CommandString[15];
int iTIUCount;
snprintf(CommandString, sizeof CommandString, "V4%03X", addr);
bool bretv = ptrTIUCommThread->Send_String_FIFO(Eng, CommandString, TIU, RemoteNo, 21, QUERY_WAIT, NO_RECORD, NULL, NULL, &iTIUCount, FBResp);
if (!bretv) {	// retry once
	if (DEBUGGING) DB->Debug_Print("FetchInt() Retrying " + String(CommandString));
	bretv = ptrTIUCommThread->Send_String_FIFO(Eng, CommandString, TIU, RemoteNo, 21, QUERY_WAIT, NO_RECORD, NULL, NULL, &iTIUCount, FBResp);
	if (!bretv) return false;	// return false with value == 0 if we can't read RAM
	}

if (bretv) {
	if (Setup->Radio_COM_Mode->Checked) {
		// Handle Radio Response
		// pull out the value         0    1    2    3
		// 965A 99A6 5555 5555 5559 5559 A596 6566 555A
		//   87   D9   00   00   04   04   E1   31   05
		//                            ^^   ^^   ^^   ^^    RAM -- returned by the TIU
		*value =  FBResp[5] << 24 | FBResp[6] << 16 | FBResp[7] << 8 | FBResp[8];
		}
	if (Setup->WTIU_WiFi_Mode->Checked) {
		// Handle WTIU ASCII Response  "qE8 00 36 0A  okay"
		//                               ^^ ^^ ^^ ^^    RAM -- returned by the TIU
		int h0, h1, h2, h3;
		int retv = sscanf(&FBResp[1], "%x %x %x %x", &h0, &h1, &h2, &h3);   // %x requires &int
		if (retv == 4) {
			*value = h0 << 24 | h1 << 16 | h2 << 8 | h3;
			} else {
			return false;
			}
		}
	}

if (DEBUGGING) DB->Debug_Print("Fetch 32 bit Integer - address : 0x" + IntToHex((int)addr, 3) + " value : 0x" + IntToHex((int)*value, 8));
return true;
}
//---------------------------------------------------------------------------
void __fastcall TMainForm::DumpMemoryButtonClick(TObject *Sender)
{
// Dump Engine memory from 0x00 to 0x6F
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
if (!DEBUGGING) return;
if (FirstLine->ValueAsInt > LastLine->ValueAsInt) return;	// FirstLine must always be <= LastLine
//
// Dumps from FIRSTLINE to LASTLINE (line = 16 bytes) of either EEPROM or a RAM memory.
// I suspect it is RAM memory as the DCH time is there (at 0x0038) and it is
// incrementing. You really can't do that with a block erase EEPROM.
//
String PLine = "Offset(h)  00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F";

DB->Debug_Print(PLine, clGreen);
CRCBuffer->SelAttributes->Color = clBlack;
CRCBuffer->Lines->Append(PLine);
ScrollEnd(CRCBuffer);   // remain at end of buffere;
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
    return;
    }
for (int i = FirstLine->ValueAsInt ; i <= LastLine->ValueAsInt ; i++) {
	PLine  = Format((String)"0x0000%s", ARRAYOFCONST((IntToHex((i * 0x10), 4))));
	for (int j = 0x00 ; j < 0x10 ; j = j + 0x04) {
    	unsigned long B;
        FetchInt((i * 0x10) + j, &B, EngineNo, MyTIUNo);
		PLine = PLine + Format((String)" %s %s %s %s", ARRAYOFCONST((
        	IntToHex((int)(B >> 24) & 0xFF, 2),
            IntToHex((int)(B >> 16) & 0xFF, 2),
            IntToHex((int)(B >>  8) & 0xFF, 2),
            IntToHex((int)(B      ) & 0xFF, 2)
			)));
    	}
    DB->Debug_Print(PLine, clGreen);
    CRCBuffer->SelAttributes->Color = clGreen;
    CRCBuffer->Lines->Append(PLine);
    ScrollEnd(CRCBuffer);   // remain at end of buffer
    PLine = "";
    }
DB->Debug_Print(PLine, clGreen);
CRCBuffer->SelAttributes->Color = clGreen;
CRCBuffer->Lines->Append("");
ScrollEnd(CRCBuffer);   // remain at end of buffer
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::FirstLineValueChanged(TObject *Sender)
{
if (FirstLine->ValueAsInt > LastLine->ValueAsInt) {
	LastLine->ValueAsInt = FirstLine->ValueAsInt;
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::LastLineValueChanged(TObject *Sender)
{
if (LastLine->ValueAsInt < FirstLine->ValueAsInt) {
	FirstLine->ValueAsInt = LastLine->ValueAsInt;
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::SetEnginePS2Click(TObject *Sender)
{
SetEnginePS2->Checked = true;
SetEnginePS3->Checked = false;
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
MainForm->EngineData->Cells[ENG_PS][EngineNo] = PS2;
PSType[EngineNo] = PS2; // information for the PC threads
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::SetEnginePS3Click(TObject *Sender)
{
SetEnginePS3->Checked = true;
SetEnginePS2->Checked = false;
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
MainForm->EngineData->Cells[ENG_PS][EngineNo] = PS3;
PSType[EngineNo] = PS3; // information for the PC threads
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::VButtonClick(TObject *Sender)
{
// test the V command
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
if (Setup->Radio_COM_Mode && Setup->TXOnlyComm->Checked) {	// cannot do this function without full duplex communications
	DebugMessagePopup("Warning", MainForm->TXMsg, "");
    return;
    }

byte VResp[CMDMAX];
// V must be AnsiString because the c_str() taken needs to be a char type
AnsiString V = Encoded_CommandA->Text;
if (V.Length() != 5) return;
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
    return;
    }
bool bretv = ptrTIUCommThread->Send_String_FIFO(EngineNo, V.c_str(), MyTIUNo, RemoteNo, 21, QUERY_WAIT, NO_RECORD, NULL, NULL, NULL, VResp);
char buff[20];

if (bretv) {
	if (Setup->Radio_COM_Mode->Checked) {     // in Radio mode
        unsigned int ival = VResp[5] << 24 | VResp[6] << 16 | VResp[7] << 8 | VResp[8];
        snprintf(buff, sizeof buff, "0x%08X", ival);
        }
    if (Setup->WTIU_WiFi_Mode->Checked) {    // in WTIU mode
        snprintf(buff, sizeof buff, "0x%2.2s%2.2s%2.2s%2.2s", &VResp[1], &VResp[4], &VResp[7], &VResp[10]);
        }
    }
if (DEBUGGING) DB->Debug_Print("V Command : " + String(buff), clGreen);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ComponentButtonClick(TObject *Sender)
{
//
// Can use a scheme like this to check in the Configuration File
// for any component name "Label4", etc and be able to change
// the component Caption to what is specified in the Configuration File
// http://bcbjournal.org/articles/vol3/9904/Enumerating_components.htm
CRCBuffer->Lines->Clear();
for (int i=0;i<ComponentCount;i++) {
	String S = Components[i]->Name;
	CRCBuffer->Lines->Append(S);
    //TLabel * T;
    //T = dynamic_cast<TLabel *> (Components[i]);
    //if (T) T->Caption = S;	// change all labels to the "Name" of the label
    // http://bcbjournal.org/articles/vol3/9904/Dealing_with_sets.htm?PHPSESSID=2f96cbddf92baa7dc77f7a33591f59b5
    //if (T) T->Font->Style = TFontStyles() << fsBold;	// change all fonts to bold
	}
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::EmailAuthorClick(TObject *Sender)
{
HINSTANCE retv = ShellExecute(NULL,TEXT("open"),TEXT("mailto:markd@silogic.com"), NULL, NULL, SW_RESTORE);
if ((int)retv <= 32) {
	// error
    if (DEBUGGING) DB->Debug_Print("Shell Execute error - return value = " + String((int) retv));
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ShrinkRecordingBufferClick(TObject *Sender)
{
RecordingBuffer->Height = 57;
RecordingBuffer->Top = 406;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ExpandRecordingBufferClick(TObject *Sender)
{
RecordingBuffer->Height = 202;
RecordingBuffer->Top = 260;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ProgramControlClick(TObject *Sender)
{
// Call up Program Control Window based on Tag value
StartProgramControl(Sender, reinterpret_cast<TMenuItem *>(Sender)->Tag);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CMPCWindowOpen(TMessage &Message)
{
// Open a window of Program Control
int NewPCNo = Message.WParam;
assert(NewPCNo >= 1 && NewPCNo <= PCMAX);
// Start Program Control and set checkmarks in menu
StartProgramControl(PCPopupMenu->Items->Items[NewPCNo - 1], NewPCNo);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::StartProgramControl(TObject *Sender, int PCNo)
{
// Show the Detected Tags window if RFID port is connected
// DetectedRFIDTags window is Auto-Create
#if NOTAGCHECKBOX
if (Setup->RFID_Mode->Checked && !DetectedRFIDTags->Visible) {
	DetectedRFIDTags->Show();
    }
#endif
//
// Start the timer thread if it is not already started
	// Create suspended - in BCB 4.0, the thread constructor does not wait for lower level constructors itself.
    // The Execute() routine actually runs before some lower level construction is finished.
    // Don't put constructions in the thread constructor. if you have to, start the thread suspended and then start it running
    // per Remy LeBeau (TeamB) it is a Known bug that was fixed in BCB 6.0
if (ptrTimer_Thread == NULL) {
	ptrTimer_Thread = new TMyTimerThread(true, NULL, 0);
    assert(ptrTimer_Thread);
	TimerThreadLED->Checked = true;		//turn on Debug LED
	#if __BORLANDC__ >= 0x720
	ptrTimer_Thread->Start();			// start the thread for CB10.1
	#else
	ptrTimer_Thread->Resume();			// start the thread for BCB4.0 - use deprecated in CB10.1, use Start() instead
	#endif
    }
// This code should work with auto create on OR off.
// ------------ With programmed create using C++ Builder's generated pointer
// Program Control Form should be manually created as it is rarely needed
if (!ptrProgram_Control[PCNo - 1]) {
    int EngineNo = EngineSpin->ValueAsInt;  // use TIU number and AIU number in spinners
    int MyTIUNo = TIUSpin->ValueAsInt;
	ptrProgram_Control[PCNo - 1] = new TProgram_Control(this, EngineNo, RemoteNo, MyTIUNo, PCNo);     // Pass "this" as the owner
	assert(ptrProgram_Control[PCNo - 1] != NULL);			// make sure that construction worked
    ptrProgram_Control[PCNo - 1]->ParentHandle = Handle; 	// for return messages
   	}
ptrProgram_Control[PCNo - 1]->Show();	// since I use a manual pointer, the form must get freed at close
if (Sender) {
	reinterpret_cast<TMenuItem *>(Sender)->Checked = true;	// add check mark to PCPopupMenu item
	}
// show the [Show Tags] button
if ((Setup->RFID_COM_Mode->Checked || Setup->RFID_WiFi_Mode->Checked) ) {
    ptrProgram_Control[PCNo - 1]->ShowDetectedTagsButton->Visible = true;
	}
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::Prog_Control_ButtonClick(TObject *Sender)
{
// ASSERTING works ok but I need to decide where I want to use it
//ASSERTING(bTIUConnected == true, TEXT("Press the [Connect] button before performing this operation"));
//ASSERTING(bTIUConnected == true, ConnectMsg.c_str());
ProgramControlClick(PCPopupMenu->Items->Items[0]);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::FormCloseQuery(TObject *Sender, bool &CanClose)
{
// In debug mode, allow disconnect
if (DEBUGGING) {
	CanClose = true;
    return;
    }
// TIU Must be disconnected from the COM ports to allow exit
if (IsRadioCOMConnected() || IsRFIDCOMConnected()){
	DebugMessagePopup("Warning", "[Disconnect] from the TIU before trying to exit", "");
	CanClose = false;
    return;
    }
CanClose = true;	// allow close
}
//---------------------------------------------------------------------------
// An Improved assert() for C++ Builder
// https://edn.embarcadero.com/article/28432
// https://codecentral.embarcadero.com/Item.aspx?id=17563
// By: David Pankhurst
// modified to run under both ANSI (BCB4.0 & BCB6.0) and UNICODE (CB10.x) OS
int HandleAsserting(TCHAR *testStr,TCHAR *msgStr,TCHAR *fileStr,int line,int *callFlag) {
    // assert message and return flag regarding aborting:
    // callFlag set if repeating forbidden
    static TCHAR s_text[199]=TEXT("");	// don't assign dynamically in
                                		// case of "out of memory" error
	wsprintf(s_text,TEXT("FAILED: %s\r\n")
					TEXT("Error: ( %s )\r\n")
					TEXT("File '%s', Line %d\r\n")
					TEXT("Abort execution, allow assert Retry, or Ignore in future?"),
					msgStr, testStr, fileStr, line);
    switch ( ::MessageBox(NULL, s_text, TEXT("ASSERTION ERROR"),MB_ABORTRETRYIGNORE) ) {
	  case IDIGNORE: // prevent calling again - turn off flag
        *callFlag=0; // never call again
        break;
      case IDABORT:  // return flag and break
        return 1;    // abort/break
	  }
return 0;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::BreakButtonClick(TObject *Sender)
{
// Test the DebugBreak using some different TCHAR data types
// This worked but I never used these functions
#ifdef ASSERTINGFUNCTIONS
TCHAR msg[128];
_TCHAR msg1[128];
_sntprintf(msg, sizeof msg, TEXT("Break ?"));
_sntprintf(msg1, sizeof msg1, _T("Break ?"));
// Both of the next two ASSERTING() statements get "W8008 Condition is always true" because they are here for testing only
ASSERTING(0 == 1, msg);					// example of use of ASSERTING with a TCHAR message
ASSERTING(1 == 0, TEXT("2nd Break?"));	// example of use of ASSERTING with a text string

// askDlg() - A convenient message dialog for C++ Builder
// Code near the beginning of Train_Control.cpp

  BOOL isCancel;
  char reportName[25] = {"One"};
  if (askDlg(this, isCancel, "Do you want to print the report %s.", reportName)) {
    //... report printing code
    MyMessageDlg(this, mtInformation, "Report %s printed.", reportName);
  }
  else
    if (isCancel) //You can use this if you want different paths for No and Cancel
      return;     //actions.
  //proceed with further action if any
#endif
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ComboBoxButtonClick(TObject *Sender)
{
// strings for  CarNodelComboBox, EngineModelComboBox and RailroadComboBox
CarModelComboBox->Visible = !CarModelComboBox->Visible;
EngineModelComboBox->Visible = !EngineModelComboBox->Visible;
RailroadNameComboBox->Visible = !RailroadNameComboBox->Visible;
EngineTypeComboBox->Visible = !EngineTypeComboBox->Visible;
TagLocationComboBox->Visible = !TagLocationComboBox->Visible;
CouplerComboBox->Visible = !CouplerComboBox->Visible;
}
//---------------------------------------------------------------------------
void __fastcall TMainForm::NameofEngineChange(TObject *Sender)
{
// Change the background color to red if the string is longer than 16 characters
// or less than or equal to one character
if (NameofEngine->Text.Length() <= 1 || NameofEngine->Text.Length() > 16) {
	// make background red
	NameofEngine->Color = clRed;
	} else {
	// make background normal
	NameofEngine->Color = clWindow;
	}
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::NameofEngineExit(TObject *Sender)
{
// when Engine Name edit loses focus, restore its value from the EngineList
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
NameofEngine->Text = EngineData->Cells[ENG_NAME][EngineNo];
FileNamePrefix->Text = NameofEngine->Text;
NameofEngine->ReadOnly = true;
NameofEngine->Cursor = crNo;
NameofEngine->Hint = "Press [Read] to read names of Engines";
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::NameofEngineKeyDown(TObject *Sender, WORD &Key,
      TShiftState Shift)
{
// handle Engine Name completion processing
switch (Key) {
	case VK_ESCAPE:
		// on [Esc], cancel edit
    	NameofEngineExit(Sender);
        EngineData->SetFocus();
        return;

	case VK_RETURN:
		// on [Enter], send new engine name to engine
        int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
		if (EngineNo > 0) EngineData->Cells[ENG_NAME][EngineNo] = NameofEngine->Text;
		FileNamePrefix->Text = NameofEngine->Text;
		// NewEngineName must be AnsiString because the c_str() taken needs to be a char type
		AnsiString NewEngineName = NameofEngine->Text.Trim();
        if (IsTIUConnected()) {
            // if we are connected, send the new engine name to the TIU
    		// send Command with exactly 16 characters for new Engine name
	    	unsigned char Command_String[19];
            RequestyCommand = true; // request a 'y' command at engine name change
    		// Limit engine name sent to TIU to exactly 16 characters
	    	snprintf((char *)Command_String, sizeof Command_String, "Ns%-16.16s", NewEngineName.c_str());	// exactly 16 characters
            int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
            if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
                MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
                return;
                }
		    if (!ptrTIUCommThread->Send_String_FIFO(EngineNo, Command_String, MyTIUNo, RemoteNo, 11, LONG_WAIT, NO_RECORD)) {
                // restore original engine name
                EngineData->Cells[ENG_NAME][EngineNo] = SavedNameofEngine;
                NameofEngine->Text = SavedNameofEngine;
                FileNamePrefix->Text = SavedNameofEngine;
    			ShowMessage("Failed to set new Engine name (Engine " + String(EngineNo) + ")");
	    	    return;
		        }
            }
    	SetConsistState(EngineNo, ACTIVE);		// set active
        // update users of the Engine List
		UpdateEngDataUsers();
        //
		NameofEngineExit(Sender);	// complete edit
        EngineData->SetFocus();
    	return;
	}
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ShowDetectedTagsClick(TObject *Sender)
{
// Show the Detected Tags window if RFID port is connected
// DetectedRFIDTags window is Auto-Create
if ((DEBUGGING || Setup->RFID_COM_Mode->Checked || Setup->RFID_WiFi_Mode->Checked) && !DetectedRFIDTags->Visible) {
	DetectedRFIDTags->Show();
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CMQuick(TMessage &Message)
{
// Open a page of Hot Buttons
int NewPageNo = Message.WParam;
assert(NewPageNo >= 1 && NewPageNo <= QUICKPAGEMAX);
DoQuickButton(NewPageNo);
}

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

void __fastcall TMainForm::QuickButtonClick(TObject *Sender)
{
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
DoQuickButton(TIUSpin->ValueAsInt);   // Always start with TIU in Spinner
}

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

void __fastcall TMainForm::DoQuickButton(int NewPageNo)
{
// Create Quick Button page
//
// This code should work with auto create on OR off.
// ------------ With programmed create using C++ Builder's generated pointer
// Quick Operations Form should be manually created as up to 5 can exist
assert(NewPageNo >= 1 && NewPageNo <= QUICKPAGEMAX);

if (ptrQuick[NewPageNo - 1]  == NULL) {
	ptrQuick[NewPageNo - 1] = new TQuickCtrl(this, RemoteNo, NewPageNo, Setup->Quick_Ctrl_Scaling->Value);     // Pass "this" as the owner
	assert(ptrQuick[NewPageNo - 1] != NULL);		// make sure that construction worked
    ptrQuick[NewPageNo - 1]->ParentHandle = Handle; // for return messages
	} else {   // it's already created
	ptrQuick[NewPageNo - 1]->Show();
	}
}
//---------------------------------------------------------------------------
void __fastcall TMainForm::PrintthisWindow1Click(TObject *Sender)
{
MainForm->PrintScale = poProportional;
MainForm->Print();
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ShowLayoutWindowClick(TObject *Sender)
{
// Create Layout Window
//
// This code should work with auto create on OR off.
// ------------ With programmed create using C++ Builder's generated pointer
// Quick Operations Form should be manually created
if (LayoutForm == NULL) {
	LayoutForm = new TLayoutForm(this);     // Pass "this" as the owner
	assert(LayoutForm != NULL);		// make sure that construction worked
    LayoutForm->ParentHandle = Handle; // for return messages
	} else {   // it's already created
	LayoutForm->ShowIt();
	}
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::QButtonClick(TObject *Sender)
{
// test the Q "Quality" command
// NOTE: the Q command always fails with a NAK. Either I am not using it correctly
// or it does not work through the radio interface to the TIU
//
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
if (Setup->Radio_COM_Mode && Setup->TXOnlyComm->Checked) {	// cannot do this function without full duplex communications
	DebugMessagePopup("Warning", MainForm->TXMsg, "");
    return;
    }

byte QResp[CMDMAX];
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
    return;
    }
bool bretv = ptrTIUCommThread->Send_String_FIFO(EngineNo, "Q", MyTIUNo, RemoteNo, 21, SHORT_WAIT, NO_RECORD, NULL, NULL, NULL, QResp);
char buff[20];

if (bretv) {
	if (Setup->Radio_COM_Mode->Checked) {     // in Radio mode
        unsigned int ival = QResp[5] << 24 | QResp[6] << 16 | QResp[7] << 8 | QResp[8];
        snprintf(buff, sizeof buff, "%d", ival);
        }
    if (Setup->WTIU_WiFi_Mode->Checked) {    // in WTIU mode
        unsigned int ival;
        sscanf(QResp, "Q%d", &ival);
        snprintf(buff, sizeof buff, "%d", ival);
        }
    }

if (DEBUGGING) DB->Debug_Print("Quality : " + String(buff), clGreen);
QButton->Caption = buff;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::EngTypeButtonClick(TObject *Sender)
{
// Fetch the Engine Type. This value, 2 bytes starting at address 0x200, is fetched
// by the DCS Loader. I don't know what it means.
//
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
if (Setup->Radio_COM_Mode && Setup->TXOnlyComm->Checked) {	// cannot do this function without full duplex communications
	DebugMessagePopup("Warning", MainForm->TXMsg, "");
    return;
    }

EngTypeButton->Caption = "Engine Type";

byte QResp[CMDMAX];
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
    return;
    }
bool bretv = ptrTIUCommThread->Send_String_FIFO(EngineNo, "V2200", MyTIUNo, RemoteNo, 21, QUERY_WAIT, NO_RECORD, NULL, NULL, NULL, QResp);
if (!bretv) {	// retry once
    if (DEBUGGING) DB->Debug_Print("EngTypeButtonClick() Retrying V2200");
	bretv = ptrTIUCommThread->Send_String_FIFO(EngineNo, "V2200", MyTIUNo, RemoteNo, 21, QUERY_WAIT, NO_RECORD, NULL, NULL, NULL, QResp);
	if (!bretv) return;	// return with no change if we can't read RAM
    }
char buff[20];
if (bretv) {
	if (Setup->Radio_COM_Mode->Checked) {     // in Radio mode
        unsigned int ival = QResp[5] << 8 | QResp[6];
        snprintf(buff, sizeof buff, "0x%04X", ival);
        }
    if (Setup->WTIU_WiFi_Mode->Checked) {    // in WTIU mode
        snprintf(buff, sizeof buff, "0x%2.2s%2.2s", &QResp[1], &QResp[4]);
        }
    }
if (DEBUGGING) DB->Debug_Print("Engine Type at 0x200 : " + String(buff), clGreen);
EngTypeButton->Caption = buff;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::EngType2ButtonClick(TObject *Sender)
{
// Fetch the Engine Type. This value, 2 bytes starting at address 0x22, is fetched
// by the start up engine sequence. With it, we can tell if the engine is
// a diesel or steam
//
// I've seen these values for the LO byte:
// 0x00 for steam, 0x05/0x85 for Diesel and Gas/Electric, 0x25 for electric
//
// I don't know what the HO byte means.
//
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
if (Setup->Radio_COM_Mode && Setup->TXOnlyComm->Checked) {	// cannot do this function without full duplex communications
	DebugMessagePopup("Warning", MainForm->TXMsg, "");
    return;
    }

EngType2Button->Caption = "Engine Type";
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
    return;
    }
byte QResp[CMDMAX];
bool bretv = ptrTIUCommThread->Send_String_FIFO(EngineNo, "V2022", MyTIUNo, RemoteNo, 21, QUERY_WAIT, NO_RECORD, NULL, NULL, NULL, QResp);
if (!bretv) {	// retry once
    if (DEBUGGING) DB->Debug_Print("EngType2ButtonClick() Retrying V2022");
	bretv = ptrTIUCommThread->Send_String_FIFO(EngineNo, "V2022", MyTIUNo, RemoteNo, 21, QUERY_WAIT, NO_RECORD, NULL, NULL, NULL, QResp);
	if (!bretv) return;	// return with no change if we can't read RAM
    }

char buff[20];

if (bretv) {
	if (Setup->Radio_COM_Mode->Checked) {     // in Radio mode
        unsigned int ival = QResp[5] << 8 | QResp[6];
        snprintf(buff, sizeof buff, "0x%04X", ival);
        }
    if (Setup->WTIU_WiFi_Mode->Checked) {    // in WTIU mode
        snprintf(buff, sizeof buff, "0x%2.2s%2.2s", &QResp[1], &QResp[4]);
        }
    }
if (DEBUGGING) DB->Debug_Print("Engine Type at 0x22 : " + String(buff), clGreen);
EngType2Button->Caption = buff;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::MarksTrainLayouts1Click(TObject *Sender)
{
HINSTANCE retv = ShellExecute(NULL, TEXT("open"), TEXT("http://www.silogic.com/trains/layout.html"), NULL, NULL, SW_SHOWNORMAL);
if ((int)retv <= 32) {
	// error
    if (DEBUGGING) DB->Debug_Print("Shell Execute error - return value = " + String((int) retv));
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::RegButtonClick(TObject *Sender)
{

String Operation = TEXT("open");
String FileName = TEXT("\"c:\\windows\\system32\\reg.exe\"");
String Parameters = TEXT("\"export\" \"HKCU\\Software\\Remote Control5\\Serial\" \"RTC.reg\" \"/y\"");
String Directory = cStartDir;
HINSTANCE retv = ShellExecute(NULL, Operation.c_str(), FileName.c_str() , Parameters.c_str(), Directory.c_str(), SW_SHOWNORMAL);
if ((int)retv <= 32) {
	// error
    if (DEBUGGING) DB->Debug_Print("Shell Execute error - return value = " + String((int) retv));
	}
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::TIU11Click(TObject *Sender)
{
AIUPopupMenu->Tag = 1;
TPoint p_rel_scr = Mouse->CursorPos;	// get xy Mouse position
AIUPopupMenu->Popup((int) p_rel_scr.x, (int) p_rel_scr.y);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::TIU21Click(TObject *Sender)
{
AIUPopupMenu->Tag = 2;
TPoint p_rel_scr = Mouse->CursorPos;	// get xy Mouse position
AIUPopupMenu->Popup((int) p_rel_scr.x, (int) p_rel_scr.y);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::TIU31Click(TObject *Sender)
{
AIUPopupMenu->Tag = 3;
TPoint p_rel_scr = Mouse->CursorPos;	// get xy Mouse position
AIUPopupMenu->Popup((int) p_rel_scr.x, (int) p_rel_scr.y);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::TIU41Click(TObject *Sender)
{
AIUPopupMenu->Tag = 4;
TPoint p_rel_scr = Mouse->CursorPos;	// get xy Mouse position
AIUPopupMenu->Popup((int) p_rel_scr.x, (int) p_rel_scr.y);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::TIU51Click(TObject *Sender)
{
AIUPopupMenu->Tag = 5;
TPoint p_rel_scr = Mouse->CursorPos;	// get xy Mouse position
AIUPopupMenu->Popup((int) p_rel_scr.x, (int) p_rel_scr.y);
}
//---------------------------------------------------------------------------
void __fastcall TMainForm::TIU61Click(TObject *Sender)
{
AIUPopupMenu->Tag = 6;
TPoint p_rel_scr = Mouse->CursorPos;	// get xy Mouse position
AIUPopupMenu->Popup((int) p_rel_scr.x, (int) p_rel_scr.y);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::TIU71Click(TObject *Sender)
{
AIUPopupMenu->Tag = 7;
TPoint p_rel_scr = Mouse->CursorPos;	// get xy Mouse position
AIUPopupMenu->Popup((int) p_rel_scr.x, (int) p_rel_scr.y);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::TIU81Click(TObject *Sender)
{
AIUPopupMenu->Tag = 8;
TPoint p_rel_scr = Mouse->CursorPos;	// get xy Mouse position
AIUPopupMenu->Popup((int) p_rel_scr.x, (int) p_rel_scr.y);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::AIUControl1Click(TObject *Sender)
{
// AIU 1
int MyTIUNo = AIUPopupMenu->Tag;
StartAIUControl(Sender, MyTIUNo, 1);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::AIUControl2Click(TObject *Sender)
{
// AIU 2
int MyTIUNo = AIUPopupMenu->Tag;
StartAIUControl(Sender, MyTIUNo, 2);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::AIUControl3Click(TObject *Sender)
{
// AIU 3
int MyTIUNo = AIUPopupMenu->Tag;
StartAIUControl(Sender, MyTIUNo, 3);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::AIUControl4Click(TObject *Sender)
{
// AIU 4
int MyTIUNo = AIUPopupMenu->Tag;
StartAIUControl(Sender, MyTIUNo, 4);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::AIUControl5Click(TObject *Sender)
{
// AIU 5
int MyTIUNo = AIUPopupMenu->Tag;
StartAIUControl(Sender, MyTIUNo, 5);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::AIU6Control1Click(TObject *Sender)
{
// AIU 6
int MyTIUNo = AIUPopupMenu->Tag;
StartAIUControl(Sender, MyTIUNo, 6);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::AIU7Control1Click(TObject *Sender)
{
// AIU 7
int MyTIUNo = AIUPopupMenu->Tag;
StartAIUControl(Sender, MyTIUNo, 7);
}
//---------------------------------------------------------------------------
void __fastcall TMainForm::AIU8Control1Click(TObject *Sender)
{
// AIU 8
int MyTIUNo = AIUPopupMenu->Tag;
StartAIUControl(Sender, MyTIUNo, 8);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::AIU9Control1Click(TObject *Sender)
{
// AIU 9
int MyTIUNo = AIUPopupMenu->Tag;
StartAIUControl(Sender, MyTIUNo, 9);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::AIU10Control1Click(TObject *Sender)
{
// AIU 10
int MyTIUNo = AIUPopupMenu->Tag;
StartAIUControl(Sender, MyTIUNo, 10);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CMAIU(TMessage &Message)
{
// Open a page of TIU/AIU Controls
int NextAIU = Message.WParam;
int NextTIU = Message.LParam;
assert(NextTIU >= 1 && NextTIU <= TIUMAX);
StartAIUControl(NULL, NextTIU, NextAIU);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::AIUButtonClick(TObject *Sender)
{
// This function uses the TIU and AIU Spinners
if (AIUSpin->ValueAsInt == 0) {
    //DebugMessage("          No AIU!         ");
    DebugMessagePopup("Warning", "          No AIU!         ");
	return;
    }
// AIUNo from AIU Spinner and TIUNo from TIU Spinner
StartAIUControl(Sender, TIUSpin->ValueAsInt, AIUSpin->ValueAsInt);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::StartAIUControl(TObject *Sender, int iTIUNo, int iAIUNo)
{
if (iAIUNo == 0) {
    //DebugMessage("          No AIU!         ");
    DebugMessagePopup("Warning", "          No AIU!         ");
	return;
    }
// Open accessories / switches form
// manually create form
assert(iTIUNo >= 1 && iTIUNo <= TIUMAX);
assert(iAIUNo >= 1 && iAIUNo <= AIUMAX);
// Many AIU Control Forms
if (ptrAIUControl[iTIUNo-1][iAIUNo-1] == NULL) {
    int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
    // Note: EngineNo is not used in the command and could possibily set to FACTORYRESET
	ptrAIUControl[iTIUNo-1][iAIUNo-1] = new TAIUControl(this, RemoteNo, EngineNo, iTIUNo, iAIUNo);   // create the Form & Pass "this" as the owner
	assert(ptrAIUControl[iTIUNo-1][iAIUNo-1] != NULL);	// make sure that construction worked
    ptrAIUControl[iTIUNo-1][iAIUNo-1]->ParentHandle = Handle; // for return messages
    ptrAIUControl[iTIUNo-1][iAIUNo-1]->Requester = Sender; // for object requesting the AIU window
	//ptrAIUControl[iTIUNo-1][iAIUNo-1]->Show();
	} else {    // it's already created, just show it
	ptrAIUControl[iTIUNo-1][iAIUNo-1]->Show();
	}
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------

//
// 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 == 0x5 || d == 0x6 || d == 0x9 || d == 0xA) 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';
}
//---------------------------------------------------------------------------
/**
 * charToHex
 *
 * 'ch' Character to be converted to hexadecimal
 *
 * Returns:
 *  Hex value, 0x00 on bad hex character
 */
//---------------------------------------------------------------------------
inline byte charToHex(byte ch)
{
byte val;
if (ch >= 'A' && ch <= 'F')
    val = ch - 55;
    else if (ch >= 'a' && ch <= 'f')
      val = ch - 87;
      else if (ch >= '0' && ch <= '9')
         val = ch - 48;
         else
         val = 0x00;
return val;
}
//---------------------------------------------------------------------------
/* Hex2Digit_to_Decimal
 *
 * convert a two digit hex character string into an 8 bit unsigned decimal number
 */
//--------------------------------------------------------------------------
inline unsigned char Hex2Digit_to_Decimal(unsigned char * str) {
// convert a 2 digit char string to decimal
unsigned char val = (charToHex(*str) << 4) & 0xF0;
val |= charToHex(*(str + 1)) & 0x0F;
return val;
}
//---------------------------------------------------------------------------

char * Amp(const char *a)
{
//
// Replace "&" with "&&" in string
//

if (a == NULL)
    return NULL;

size_t a_l = strlen(a);
size_t count = 0;
for (size_t i = 0;  i < a_l;  ++i) {
    if (a[i] == '&') {
        count++;
	    }
	}
// Grok found that there was an error in the next line
//char *o = (char *) malloc(a_l + 1 * count + 1); // 1 is because you add 2 new symbols, but remove 1 so 2 - 1 = 1
char *o = (char *) malloc(a_l + count + 1);  // Grok said number of bytes is incorrect
                                            // Grok said casting was not necessary but CB10.1 required it
if (o == NULL)
        return NULL;               // handle allocation failure

size_t index = 0;
for (size_t i = 0;  i < a_l;  ++i) {
    if (a[i] != '&')
        o[index++] = a[i];
    else {
        o[index++] = '&';
        o[index++] = '&';
    	}
	}
o[index++] = '\0';
// return pointer value must be free()d by caller once the string is copied
return o;
}
#ifdef GROK
#include <stdlib.h>
#include <string.h>

/*
 * Amp - Replace every '&' with '&&' in the input string.
 *
 * Returns a newly allocated string that must be freed by the caller
 * using free() when no longer needed.
 *
 * Returns NULL if:
 *   - input string is NULL, or
 *   - memory allocation fails.
 */
char *Amp(const char *a)
{
    if (a == NULL)
        return NULL;

    // We allocate enough space for the worst-case scenario:
    // every character in the input is '&', so each becomes '&&'.
    // +1 for the terminating null character.
    size_t len = strlen(a);
    char *o = malloc(len * 2 + 1);
    if (o == NULL)
        return NULL;                    // allocation failed

    char *p = o;                        // destination pointer (write position)

    // Single pass: copy characters, doubling each '&'
    while (*a) {
        if (*a == '&') {
            *p++ = '&';
            *p++ = '&';
        } else {
            *p++ = *a;
        }
        a++;                            // advance input pointer
    }

    *p = '\0';                          // null-terminate the result

    return o;
}

#endif
//---------------------------------------------------------------------------

/* TITLE: A convenient message dialog for C++ Builder

If you often wished for a nice printf like function to display messages 
in C++ Builder, here is some code that I wrote. I suggest that you create 
a separate unit and add this code to it. Copy the function prototypes of 
MyMessageDlg and askDlg to the header file and you are ready to use them. 

Example of usage:
  BOOL isCancel;
  if (askDlg(this, isCancel, "Do you want to print the report %s.", reportName)) {
    //... report printing code
    MyMessageDlg(this, mtInformation, "Report %s printed.", reportName); 
  }
  else
    if (isCancel) //You can use this if you want different paths for No and Cancel
      return;     //actions.
  //proceed with further action if any
  ...

The advantage is that you can use printf like arguments for the message. 
Also, it automatically centers itself in the center of the parent form
passed as the first parameter. Of course, it depends on the values of 
assumedWidthOfMsgDlg and assumedHeightOfMsgDlg. If you make any
improvements to this code, please send them to delphistuff@kanadepro.com.

The second function askDlg can be used to ask a question in the printf 
style arguments. 

Note that this code uses a message buffer of 1024 bytes. If you are 
expecting larger messages, increase the buffer size or change the code
to allocate a larger buffer dynamically.
*/
//---------------------------------------------------------------------------

int msgx, msgy;

//---------------------------------------------------------------------------
BOOL getProperMessageDlgPos(TForm *parent) {
  int assumedWidthOfMsgDlg = 500;
  int assumedHeightOfMsgDlg = 160;

  if (parent==NULL)
    parent = Application->MainForm;
  if (parent==NULL)
    return FALSE;  
  msgx = parent->Left + 100;
  msgy = parent->Top + 100;

  if (msgx + assumedWidthOfMsgDlg > Screen->Width)
    return FALSE;
  if (msgy + assumedHeightOfMsgDlg > Screen->Height)
    return FALSE;

  return TRUE;
}

//---------------------------------------------------------------------------
void MyMessageDlg(TForm *parent, TMsgDlgType DlgType, const char *format,...) {
  char msgBuf[1024];

  va_list arg_ptr;

  va_start(arg_ptr,format);
  vsprintf(msgBuf,format,arg_ptr);
  va_end(arg_ptr);

  if (!getProperMessageDlgPos(parent))
    MessageDlg(msgBuf, DlgType, TMsgDlgButtons() << mbOK, 0);
  else
    MessageDlgPos(msgBuf, DlgType, TMsgDlgButtons() << mbOK, 0, msgx, msgy);
}

//---------------------------------------------------------------------------
BOOL askDlg(TForm *parent, BOOL& isCancel, const char *format,...) {
  char msgBuf[1024];

  va_list arg_ptr;

  va_start(arg_ptr,format);
  vsprintf(msgBuf,format,arg_ptr);
  va_end(arg_ptr);

  isCancel = FALSE;

  int retcode;

  if (!getProperMessageDlgPos(parent))
    retcode = MessageDlg(msgBuf, mtConfirmation,
        TMsgDlgButtons() << mbYes << mbNo << mbCancel, 0);
  else
    retcode = MessageDlgPos(msgBuf, mtConfirmation, 
       TMsgDlgButtons() << mbYes << mbNo << mbCancel, 0,
        msgx, msgy);

  switch (retcode) {
    case mrNo:
      return FALSE;
    case mrCancel:
      isCancel = TRUE;
      return FALSE;
    default:  ;
  }

  return TRUE;
}
//---------------------------------------------------------------------------

/*
DISCLAIMER 

THE INFORMATION AND MATERIAL CONTAINED IN THIS ARTICLE IS PROVIDED "AS 
IS," WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING WITHOUT 
LIMITATION ANY WARRANTY CONCERNING THE ACCURACY, ADEQUACY, OR COMPLETENESS
OF SUCH INFORMATION OR MATERIAL OR THE RESULTS TO BE OBTAINED FROM USING 
SUCH INFORMATION OR MATERIAL. AUTHOR SHALL NOT BE RESPONSIBLE FOR ANY CLAIMS
ATTRIBUTABLE TO ERRORS, OMISSIONS, OR OTHER INACCURACIES IN THE INFORMATION 
OR MATERIAL CONTAINED IN THIS ARTICLE, AND IN NO EVENT SHALL THE AUTHOR BE
LIABLE FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
ARISING OUT OF THE USE OF SUCH INFORMATION OR MATERIAL.

copyright (C) 2000, Sanjay Kanade
http://kanadepro.com/delphistuff/
*/
//---------------------------------------------------------------------------

void __fastcall TMainForm::TIULineCountClick(TObject *Sender)
{
TIULineCount->Caption = "0";    
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------

void __fastcall TMainForm::RFIDudpRXChangeState(TObject *Sender,
      TSocketState OldState, TSocketState NewState)
{
// handle change of state
if (DEBUGGINGX(9)) DB->Debug_Print("RFID UDP RX WiFi Change State = " + String(OldState) + " to " + String(NewState));
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::RFIDudpRXSessionConnected(TObject *Sender,
      WORD ErrCode)
{
if (ErrCode != 0) {
	DB->Debug_Print("Can't connect to RFID UDP RX WiFi. Error #" + String(ErrCode));
	} else {
	ConnectedRFIDLED->Checked = true;
	//ConnectedRFIDLED->Flashing = false;
	bRFIDUDPWiFiConnected = true;
	bRFIDUDPWiFiRXConnected = true;
	if (DEBUGGINGX(9)) DB->Debug_Print("RFID UDP RX WiFi connected to Port " + RFIDudpRX->Port);
	}
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::RFIDudpRXSessionClosed(TObject *Sender,
      WORD ErrCode)
{
ConnectedRFIDLED->Checked = false;
//ConnectedRFIDLED->Flashing = false;
bRFIDUDPWiFiConnected = false;
bRFIDUDPWiFiRXConnected = false;
if (DEBUGGINGX(9)) DB->Debug_Print("RFID UDP RX WiFi disconnected from Port " + RFIDudpRX->Port);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::RFIDudpRXDataAvailable(TObject *Sender,
      WORD ErrCode)
{
if (ErrCode != 0) {
	return;
	}

char cBuffer[1024];
int Len;

Len = RFIDudpRX->Receive(cBuffer, sizeof(cBuffer));
if (Len <  0) {
    // ignore packets in error
    return;
    }
if (DEBUGGINGX(9)) {
	DB->Debug_Print("RFID UDP RX WiFi Sin Port = " + String(RFIDudpRX->PeerPort));
	DB->Debug_Print("RFID UDP RX WiFi Dotted Sin Address = " + RFIDudpRX->PeerAddr);
	}

if (Len == 0) {
    if (DEBUGGING) DB->Debug_Print("RFID UDP RX WiFi Connection Closed in OnDataAvaiable()");
    return;
    }

if (Len > 0) {
    cBuffer[Len] = '\0';
    //
    if (DEBUGGINGX(5)) DB->Debug_Print("RFID UDP Data Available : " + String(cBuffer));
    // send tag to MainForm
    char * pBuffer = new char[(CMDMAX - 10)];	// NOTE: pbuffer is delete[]d in the CM_PROCESSRFIDDATA message handler CMProcessRFIDData()
    assert(pBuffer != NULL);
    strncpy(pBuffer, cBuffer, (CMDMAX - 10));
    // Send CM_PROCESSRFIDDATA message to MainForm which will handle the incoming data in pBuffer
    //PostMessage(MainForm->Handle, CM_PROCESSRFIDDATA, '\n', (long)pBuffer); // WiFi RFID Full Packet
    (void) Perform(CM_PROCESSRFIDDATA, '\n', (long)pBuffer); // WiFi RFID Full Packet
    // pBuffer is deleted in the MainForm Message Handler
	}
}
//---------------------------------------------------------------------------
void __fastcall TMainForm::RFIDtcpRXChangeState(TObject *Sender,
      TSocketState OldState, TSocketState NewState)
{
//OnChangeState 		When the component state changes.
if (DEBUGGINGX(9)) DB->Debug_Print("RFID TCP RX WiFi Change State = " + String(OldState) + " to " + String(NewState));
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::RFIDtcpRXSessionConnected(TObject *Sender,
      WORD ErrCode)
{
//OnSessionConnected 		Session to host etablished. 
if (ErrCode != 0) {
	if (DEBUGGING) DB->Debug_Print("Can't connect to TCP RFID RX WiFi. Error #" + String(ErrCode));
	} else {
	ConnectedRFIDLED->Checked = true;
	//ConnectedRFIDLED->Flashing = false;
	//bRFIDTCPWiFiConnected = true;
	//RFIDTCPWiFiRXConnected = true;
	if (DEBUGGINGX(5)) DB->Debug_Print("RFID TCP RX WiFi connected to Port " + RFIDtcpRX->Port);
	}
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::RFIDtcpRXClientConnect(TObject *Sender,
      TWSocketClient *Client, WORD Error)
{
	//OnClientConnect 		When a new client is connecting.
    // intialize the newly instantiated client component
    if (DEBUGGINGX(9)) DB->Debug_Print("RFID TCP Client connecting: " + ((TTcpSrvClient *)Client)->PeerAddr);
    ((TTcpSrvClient *)Client)->LineMode        = TRUE;
    ((TTcpSrvClient *)Client)->LineEdit        = TRUE;
    ((TTcpSrvClient *)Client)->OnDataAvailable = RFIDtcpRXDataAvailable;
    ((TTcpSrvClient *)Client)->OnBgException   = RFIDtcpRXBgException;
    ((TTcpSrvClient *)Client)->ConnectTime     = Now();
    // NOTE: When using Mircosoft Telnet Client, that client will not show
    // it is connected if the Banner is a blank string. I think that is because
    // it expects a \r\n. That end of line is not sent if Banner is blank.
    // When I press LEDOn or LEDOff, the Microsoft
    // Telnet Client shows the sent string and starts working as expected
    // though, apparently, there is still no end of line.
    // My test ESP8266 script TCPClient.ino works without the eof of line.
   	((TTcpSrvClient *)Client)->Banner     = "";
    if (DEBUGGINGX(5)) DB->Debug_Print("RFID TCP Client #" + String(RFIDtcpRX->ClientCount) + " connect: " + ((TTcpSrvClient *)Client)->PeerAddr + "    " +
        "Duration: " + FormatDateTime("hh:nn:ss",
        Now() - ((TTcpSrvClient *)Client)->ConnectTime));
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::RFIDtcpRXClientDisconnect(TObject *Sender,
      TWSocketClient *Client, WORD Error)
{
//OnClientDisconnect 		When a client disconnects.
    if (DEBUGGINGX(5)) DB->Debug_Print("RFID TCP Client disconnect: " + ((TTcpSrvClient *)Client)->PeerAddr + "    " +
        "Duration: " + FormatDateTime("hh:nn:ss",
		Now() - ((TTcpSrvClient *)Client)->ConnectTime));
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::RFIDtcpRXBgException(TObject *Sender,
      Exception *E, bool &CanClose)
{
//OnBgException 		When a background exception occurs.
    if (DEBUGGINGX(5)) DB->Debug_Print("RFID TCP Client exception occured: " + E->ClassName() + ": " + E->Message);
    CanClose = TRUE;   // Goodbye client !
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::RFIDtcpRXSessionAvailable(TObject *Sender,
      WORD ErrCode)
{
//OnSessionAvailable 		Client connected to this server.
if (ErrCode) {
    if (DEBUGGING) DB->Debug_Print("RFID TCP Session available, error #" + IntToStr(ErrCode));
    } else {
    if (DEBUGGINGX(5)) DB->Debug_Print("RFID TCP Session Available");
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::RFIDtcpRXSessionClosed(TObject *Sender,
      WORD ErrCode)
{
//OnSessionClosed 		Socket has closed.
if (ErrCode != 0) {
	if (DEBUGGING) DB->Debug_Print("Can't close RFID TCP RX WiFi Session. Error #" + String(ErrCode));
	} else {
	ConnectedRFIDLED->Checked = false;
	//ConnectedRFIDLED->Flashing = false;
	//bRFIDTCPWiFiConnected = false;
	//RFIDTCPWiFiRXConnected = false;
	if (DEBUGGINGX(5)) DB->Debug_Print("RFID TCP RX WiFi disconnected from Port " + RFIDtcpRX->Port);
	}
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::RFIDtcpRXDataAvailable(TObject *Sender,
      WORD ErrCode)
{
//OnDataAvailable 		Data available in internal receive buffer. 


TTcpSrvClient *Client;
Client = (TTcpSrvClient *)Sender;						// current client in contact with us
if (ErrCode != 0) {
	return;
	}
// We use line mode. We will receive complete lines
Client->RcvdLine = Client->ReceiveStr();
// Remove trailing CR/LF
/* Original code
while ((Client->RcvdLine.Length() > 0) &&
        ((Client->RcvdLine[Client->RcvdLine.Length()] == '\r') ||
        (Client->RcvdLine[Client->RcvdLine.Length()] == '\n'))) {
    Client->RcvdLine = Client->RcvdLine.SubString(1, Client->RcvdLine.Length() - 1);
    }
*/
// Suggested by ChatGPT
int len = Client->RcvdLine.Length();
while (len > 0 &&
      (Client->RcvdLine[len] == '\r' || Client->RcvdLine[len] == '\n')) {
    len--;
}
Client->RcvdLine.SetLength(len);
//
if (DEBUGGINGX(6)) {
    DB->Debug_Print("RFID TCP Sin Port: " + String(Client->PeerPort));
    DB->Debug_Print("RFID TCP Dotted Sin Address =  : " + Client->PeerAddr);
    }

if (Client->RcvdLine.Length() > 0) {
    //
    if (DEBUGGINGX(6)) {
        DB->Debug_Print("RFID TCP Data Available : " + Client->RcvdLine);
        }
    // send tag to MainForm
    char * pBuffer = new char[(1024 - 10)];	// NOTE: pbuffer is delete[]d in the CM_PROCESSRFIDDATA message handler CMProcessRFIDData()
    assert(pBuffer != NULL);
	strncpy(pBuffer, Client->RcvdLine.c_str(), (1024 - 10));
    // Send CM_PROCESSRFIDDATA message to MainForm which will handle the incoming data in pBuffer
    //PostMessage(MainForm->Handle, CM_PROCESSRFIDDATA, '\n', (long)pBuffer); // WiFi RFID Full Packet
    (void) Perform(CM_PROCESSRFIDDATA, '\n', (long)pBuffer); // WiFi RFID Full Packet
    // pBuffer is deleted in the MainForm Message Handler
	}
}
//---------------------------------------------------------------------------
// TTcpSrvClient is the class which will be instanciated by server component
// for each new client. N simultaneous clients means N TTcpSrvClient will be
// instanciated. Each being used to handle only a signle client.
// We can add any data that has to be private for each client, such as
// receive buffer or any other data needed for processing.
__fastcall TTcpSrvClient::TTcpSrvClient(TComponent* Owner)
    : TWSocketClient(Owner)
{
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::NumDetectorsClick(TObject *Sender)
{
// for debugging, a click sets these two values
NumDetectors->Value  = 11;
NumReaders->Value  = 15;
}
//---------------------------------------------------------------------------

int __fastcall TMainForm::GetLashUpEngine(char * LashUpEngineList, int Spot)
{
// extract an engine number from a LashUp Engine List.
// First engine (head engine) is in Spot 0
// returns 0 on error otherwise returns the Engine # in Spot
// Removes the Reverse Running bit
//
// For DCS V5 and lower, the string is in binary     eg for 12,18 the string is ",\r\x013ÿ
// RTC does not support the binary format
//
// For DCS V6 and higher for both the Remote and the WTIU WiFi,
//                the string is in ASCII             eg for 12,18 the string is ",0D13ÿ
//
// LashUpEngineList, the engine number is DCS engine number so need to subtract one to get Remote/App Engine #
//
int LashUpEngineNo;

int retv = sscanf(&LashUpEngineList[(Spot * 2) + 1], "%2X", &LashUpEngineNo);   // V5 and higher - Remote and WTIU engine list is in ASCII
if (retv != 1) {
    return 0;	// extraction failed
    }
LashUpEngineNo = (LashUpEngineNo & 0x7F) - 1;	    // GETLASHUPENGINE convert from DCS Engine Number to Engine Number

// This was the code to extract engine numbers for DCS V5 and lower
//LashUpEngineNo = (LashUpEngineList[Spot + 1] & 0x7F) - 1;  // GETLASHUPENGINE Radio engine list is in binary

if (LashUpEngineNo < ENGINEMIN || LashUpEngineNo > ENGINEMAX) return 0; // extraction failed
return LashUpEngineNo;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::LayoutPowerOffButtonClick(TObject *Sender)
{
// Turn off layout power from selected TIU in TIU Spinner
// Note: EngineNo is not used in the command and could possibily be set to FACTORYRESET
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner but I don't think the command cares
// This function uses the TIU Spinner
(void) ptrTIUCommThread->Send_String_FIFO(EngineNo, "o0", TIUSpin->ValueAsInt, RemoteNo, 11, SHORT_WAIT, NO_RECORD);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::LayoutPowerOnButtonClick(TObject *Sender)
{
// Turn on layout power from selected TIU in TIU Spinner
// Note: EngineNo is not used in the command and could possibily be set to FACTORYRESET
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner but I don't think the command cares
// This function uses the TIU Spinner
(void) ptrTIUCommThread->Send_String_FIFO(EngineNo, "o1", TIUSpin->ValueAsInt, RemoteNo, 11, SHORT_WAIT, NO_RECORD);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::OpentheActiveTIUSelectionWindowClick(TObject *Sender)
{
// Open the Active TIU Selection Window
Setup->TIUSelectionButtonClick(Sender);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::OpentheSuperTIUModeWindowClick(TObject *Sender)
{
// Open the Super TIU Mode Window
Setup->SuperModeSetupButtonClick(Sender);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::QualityButtonClick(TObject *Sender)
{
//
// I never got this to work over the Radio or over WiFi
// It may only work on RS232 connection from the DCS Loader
//
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
if (Setup->Radio_COM_Mode && Setup->TXOnlyComm->Checked) {	// cannot do this function without full duplex communications
	DebugMessagePopup("Warning", MainForm->TXMsg, "");
   	QualityButton->Caption = "Quality";
    return;
    }

// Add Engine
byte QbyteResp[CMDMAX]; 	// Array for decoded binary response (Radio) or ASCII (WiFi)
int iTIUCount;
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
    return;
    }
ptrTIUCommThread->Send_String_FIFO(EngineNo, "Q", MyTIUNo, RemoteNo, 15, SHORT_WAIT, NO_RECORD, NULL, NULL, &iTIUCount, QbyteResp);
// Returns a number between 0-100 which is some ranking of the Signal Quality on the track
// NOTE : not debugged
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ViewStartFolderClick(TObject *Sender)
{
//
HINSTANCE retv = ShellExecute(Handle,
       	NULL,
        TEXT("explorer"),		// Program to Run
        cStartDir,			// Passed Parameter
        NULL,	// Directory
        SW_SHOW);
if ((int)retv <= 32) {
	// error
    if (DEBUGGING) DB->Debug_Print("Shell Execute error - return value = " + String((int) retv));
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::CMShowWatchdog(TMessage &Message)
{
// Open a Watchdog Signal Button Window
int NewTIUNo = Message.WParam;
assert(NewTIUNo >= 1 && NewTIUNo <= TIUMAX);
int NewEngineNo = Message.LParam;
assert(NewEngineNo >= FACTORYRESET && NewEngineNo <= ENGINEMAX);
//
ShowWatchdogButton(NewTIUNo, NewEngineNo);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ShowWatchdogTIUSpinnerMenuItemClick(TObject *Sender)
{
// Open a Watchdog Signal Button Window
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
// if selected TIU is not connected fail
if (!TIUSelection[TIUSpin->ValueAsInt - 1]) {
	DebugMessagePopup("Warning", MainForm->TIUActiveMsg, "");
	return;
    }    
if (TIUSelection[TIUSpin->ValueAsInt - 1]) {  // Active TIU only
    ShowWatchdogButton(TIUSpin->ValueAsInt, EngineSpin->ValueAsInt);  // Always start with TIU in Spinner
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ShowWatchdogMenuItemClick(TObject *Sender)
{
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }

int EngineNo = EngineData->Row;
int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
    return;
    }
if (TIUSelection[MyTIUNo - 1]) {  // Active TIU only
    ShowWatchdogButton(MyTIUNo, EngineNo);  // Use TIU associated with selected engine
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ShowWatchdogButton(int NewTIUNo, int EngineNo)
{
// Create Watchdog Button page
//
// This code should work with auto create on OR off.
// ------------ With programmed create using C++ Builder's generated pointer
// Watchdog Form should be manually created as up to TIUMAX can exist
if (TIUSelection[NewTIUNo - 1]) {   // display Active TIU only
    if (ptrWatch[NewTIUNo - 1]  == NULL) {
	    ptrWatch[NewTIUNo - 1] = new TWatchdog(this, RemoteNo, NewTIUNo, EngineNo);     // Pass "this" as the owner
    	assert(ptrWatch[NewTIUNo - 1] != NULL);		// make sure that construction worked
        ptrWatch[NewTIUNo - 1]->ParentHandle = Handle; // for return messages
#if __BORLANDC__ >= 0x720
// Defined for CB10.1 C++ programs that use the VCL.
        ptrWatch[NewTIUNo - 1]->PopupMode = pmExplicit;
        ptrWatch[NewTIUNo - 1]->PopupParent = MainForm;
        ptrWatch[NewTIUNo - 1]->Position =  poDesigned;
#endif
       	ptrWatch[NewTIUNo - 1]->Show();
	    } else {   // it's already created
#if __BORLANDC__ >= 0x720
// Defined for CB10.1 C++ programs that use the VCL.
        ptrWatch[NewTIUNo - 1]->PopupMode = pmExplicit;
        ptrWatch[NewTIUNo - 1]->PopupParent = MainForm;
        ptrWatch[NewTIUNo - 1]->Position =  poDesigned;
#endif
    	ptrWatch[NewTIUNo - 1]->Show();
	    }
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::EditTIUNumberClick(TObject *Sender)
{
//
// Edit TIU Number associated with this engine - popup menu item
//
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner
if ((EngineNo == 0) && (Setup->AllowZero->Checked == false)) return;

int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
    return;
    }
//

NumberQuery->Caption = "Edit TIU Number associated with Engine # " + String(EngineNo);
NumberQuery->Question->Caption = "Select the New TIU Number";
NumberQuery->SpinLabel->Caption = "New TIU Number:";
NumberQuery->NumberSpin->MaxValue = TIUMAX;
NumberQuery->NumberSpin->MinValue = 1;
NumberQuery->NumberSpin->Value = MyTIUNo;	// current assoicated TIU number
NumberQuery->NumberSpin->Visible = true;
#if __BORLANDC__ >= 0x720
// Defined for CB10.1 C++ programs that use the VCL.
NumberQuery->PopupMode = pmExplicit;
NumberQuery->PopupParent = MainForm;
NumberQuery->Position =  poDesigned;
#endif
int retv = NumberQuery->ShowModal();
if (retv == mrCancel) return;
int NewTIUNumber = NumberQuery->NumberSpin->Value;
int OldTIUNumber = MyTIUNo;
if (OldTIUNumber == NewTIUNumber) return;		// no change
//
if (DEBUGGING) DB->Debug_Print("Changing Associated TIU #" + String(OldTIUNumber) + " to #" + String(NewTIUNumber) + " for Engine #" + String(EngineNo));

// change the associated TIU number we are using
EngineData->Cells [TIU_NUMBER] [EngineNo] = NewTIUNumber;
// Redraw the engine list
EngineData->Repaint();
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ShowNotification(String Msg)
{
#if __BORLANDC__ >= 0x720 && ENABLENOTIFICATIONS
	TNotification *MyNotification;
	MyNotification = new TNotification;
	try
	{
		MyNotification->Name = "RTC";
		MyNotification->Title = "Remote Train Control";
		MyNotification->AlertBody = Msg;

		RTCNotificationCenter->PresentNotification(MyNotification);
	}
	__finally
	{
		delete MyNotification;
	}
#endif
}
//---------------------------------------------------------------------------


void __fastcall TMainForm::FBSButton1Click(TObject *Sender)
{
// test the FBS "Boiler Start" command
//
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
if (Setup->Radio_COM_Mode && Setup->TXOnlyComm->Checked) {	// cannot do this function without full duplex communications
	DebugMessagePopup("Warning", MainForm->TXMsg, "");
    return;
    }

byte FBSResp[CMDMAX];
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
    return;
    }
ptrTIUCommThread->Send_String_FIFO(EngineNo, "W1000", MyTIUNo, RemoteNo, 21, SHORT_WAIT, NO_RECORD, NULL, NULL, NULL, FBSResp);
if (DEBUGGING) DB->Debug_Print("FBS Boiler Startup", clGreen);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::FPRButton1Click(TObject *Sender)
{
// test the FPR "Boiler Pressure Release" command
//
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
if (Setup->Radio_COM_Mode && Setup->TXOnlyComm->Checked) {	// cannot do this function without full duplex communications
	DebugMessagePopup("Warning", MainForm->TXMsg, "");
    return;
    }

byte FPRResp[CMDMAX];
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
int MyTIUNo = EngineData->Cells [TIU_NUMBER] [EngineNo].ToIntDef(0);
if (MyTIUNo == 0) {     // Make sure Engine was found on the layout
    MainForm->DebugMessagePopup("Warning", MainForm->TIUNoEngineMsg, "Engine # " + String(EngineNo));
    return;
    }
ptrTIUCommThread->Send_String_FIFO(EngineNo, "W800", MyTIUNo, RemoteNo, 21, SHORT_WAIT, NO_RECORD, NULL, NULL, NULL, FPRResp);
if (DEBUGGING) DB->Debug_Print("FPR Boiler Pressure Release", clGreen);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::DisplayVarClick(TObject *Sender)
{
// if TIU Port is not connected fail
if (!IsTIUConnected()) {
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
assert(TIUSpin->ValueAsInt >= 1 && TIUSpin->ValueAsInt <= TIUMAX);
// if selected TIU is not connected fail
if (!TIUSelection[TIUSpin->ValueAsInt - 1]) {
	DebugMessagePopup("Warning", MainForm->TIUActiveMsg, "");
	return;
    }
const TMenuItem * TMenuItemUsed = (TMenuItem *)Sender;
assert(TMenuItemUsed);
// open Var control based on menu item selected
if (TMenuItemUsed->Name.Pos("1")) {
	StartUpVarControl(1);
    }
if (TMenuItemUsed->Name.Pos("2")) {
	StartUpVarControl(2);
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::EngineUpDownClick(TObject *Sender, TUDBtnType Button)
{
// A bug in BCB4.0 - when this routine is called, the value of Position has
// already changed. Correct operation in CB10.1 "use the OnClick event handler
//to respond to changes just before the value of Position is changed."
if (Button == Comctrls::btPrev) {
    if (EngineData->Row < (EngineData->RowCount - 1))
		EngineData->Row = EngineData->RowCount - EngineUpDown->Position + UPDOWNBUGFIX;
	}
if (Button == Comctrls::btNext) {
    if (EngineData->Row > 1 )
    	EngineData->Row = EngineData->RowCount - EngineUpDown->Position - UPDOWNBUGFIX;
	}
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::EngineDataClick(TObject *Sender)
{
if (EngineData->Row == -1 || EngineData->Col == -1) {
	EngineData->Row = ENG_TOPROW;  	// ignore out of bounds (if possible)
    EngineUpDown->Position = EngineData->RowCount - ENG_TOPROW;
    }
// Set the Engine Number into the Control and the working variable
EngineSpin->ValueAsInt = EngineData->Row;	// causes EngineNo to change
int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number

if (PopOutEngineList) {   // reflect the change onto the popout engine list
    PopOutEngineList->RefreshClick(Sender);			// MCD  fill the popout engine list and active engine list
    }
// set the Engine Name into the Control
NameofEngine->Text = EngineData->Cells[ENG_NAME][EngineNo];
FileNamePrefix->Text = NameofEngine->Text;

// Enable buttons if an operations form is created
if (ptrOp[EngineNo] != NULL) {
	SoundCtrlsButton->Enabled = true;
	OpButton->Enabled = true;
	} else {
	SoundCtrlsButton->Enabled = false;
	OpButton->Enabled = false;
	}
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::EngineDataMouseDown(TObject *Sender,
      TMouseButton Button, TShiftState Shift, int X, int Y)
{
int ARow, ACol;
if (Button == mbRight || Button == mbMiddle) {
	EngineData->MouseToCell(X, Y, ACol, ARow);
    EngineData->Row = ARow;
	EngineData->Col = ACol;
    }

EngineDataClick(Sender);
// Popup the PopupMenu
if (Button == mbRight) {
	EngineData->PopupMenu->Popup(Left + X, Top + Y);
    }
// Pop Out the Engine List
if (Button == mbMiddle) {
    PopOutEngineList->RefreshClick(NULL);
	PopOutEngineList->Visible = true;
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::EngineDataDblClick(TObject *Sender)
{
// Startup the Engine or if field is empty, Edit Engine Name
if (!IsTIUConnected()){
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }
// double clicking in column header or row header is ignored
if (EngineData->Col == ENG_NUM || EngineData->Row == ENG_LABEL_ROW) return;

// Engine Name column double clicked
if (EngineData->Col == ENG_NAME) {
	if (EngineData->Cells[ENG_NAME][EngineData->Row] == "*") {
	    EditEngineName->OnClick(EditEngineName);  // edit engine name
        return;
    	}
    }
// otherwise, startup the Engine
StartupButton->OnClick(StartupButton);
}
//---------------------------------------------------------------------------
void __fastcall TMainForm::EngineDataKeyDown(TObject *Sender, WORD &Key,
      TShiftState Shift)
{
// if ENTER is pressed, startup the Engine
if (Key == VK_RETURN) {
    int EngineNo = EngineSpin->ValueAsInt;  // use engine number in spinner and its associated TIU number
	if (EngineData->Cells[ENG_NAME][EngineNo] != "*") {
		StartupButton->OnClick(StartupButton);
    	} else {
    	// Edit the Engine Name Field
    	EditEngineName->OnClick(EditEngineName);
    	}
    }
}

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

void __fastcall TMainForm::EngineDataDrawCell(TObject *Sender, int ACol,
      int ARow, TRect &Rect, TGridDrawState State)
{
// Draw the contents of each cell
EngineData->Canvas->Font->Size = 12;
// http://bcbjournal.org/articles/vol3/9904/Dealing_with_sets.htm?PHPSESSID=2f96cbddf92baa7dc77f7a33591f59b5
EngineData->Canvas->Font->Style = EngineData->Canvas->Font->Style >> fsItalic;
// if the cell is selected, highlight it
if( State.Contains(gdSelected) ) {
    EngineData->Canvas->Font->Color = clWhite;
	EngineData->Canvas->Brush->Color = clBlue;		// White on Blue
	} else {
    if (ptrOp[ARow] != NULL) {
		EngineData->Canvas->Font->Color = clBlack;	// Started up Engines are Black on Lime
		EngineData->Canvas->Brush->Color = clLime;
    	} else {
		EngineData->Canvas->Font->Color = clBlack;	// Normal Black on White
		EngineData->Canvas->Brush->Color = clWhite;
        }
	}
if ((ARow != 0) && (ARow != ALLENGINE) && (ACol == ENG_NAME) && (EngineData->Cells[ENG_STATE][ARow] == INACTIVE)) {
	// if Engine is inactive make the cell Italic.
    // Skip the column title (Row 0) and All Engine Operation (Row 100)
    EngineData->Canvas->Font->Style = EngineData->Canvas->Font->Style << fsItalic;
 	}
// move a little to the right
Rect.Left = Rect.Left + LISTLEFTOFFSET;
// fill the rectangle
EngineData->Canvas->FillRect(Rect);
// draw the text
String text;
//if (DEBUGGING && (ACol == ENG_NAME) && (ARow != ENG_LABEL_ROW) && (ARow != ENG_ALLROW) && (EngineData->Cells[ENG_NAME][ARow] != "*") &&
//        (EngineData->Cells[ENG_STATE][ARow] == ACTIVE)) {
if ((ACol == ENG_NAME) && (ARow != ENG_LABEL_ROW) && (ARow != ENG_ALLROW) && (EngineData->Cells[ENG_NAME][ARow] != "*")) {
    text = EngineData->Cells[ACol][ARow] + "  TIU" + String(EngineData->Cells[TIU_NUMBER][ARow]);
    } else {
    text = EngineData->Cells[ACol][ARow];
    }
EngineData->Canvas->TextRect(Rect, Rect.Left, Rect.Top, text);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::OpenPopOutEngineList1Click(TObject *Sender)
{
// Pop Out the Engine List
if (PopOutEngineList == NULL) return;
// cannot assign a TStringGrid to a TStringGrid : PopOutEngineList->EngineData->Assign(EngineData);
for (int I = 0; I < PopOutEngineList->EngineData->ColCount; I++)
    PopOutEngineList->EngineData->Cols[I]->Assign(EngineData->Cols[I]);
PopOutEngineList->EngineData->Row = EngineData->Row;
PopOutEngineList->EngineData->Col = EngineData->Col;
PopOutEngineList->Engine_List_Label->Caption = Engine_List_Label->Caption;
// Make sure the selected engine is visible in the middle row if possible
if (EngineData->Row < 3)
	PopOutEngineList->EngineData->TopRow = 1;
    else
	PopOutEngineList->EngineData->TopRow = EngineData->Row - 2;  // move the selected engine into view
PopOutEngineList->Visible = true;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::FillActEngLst(void)
{
// CKR Routine to populate the active engine list

/*
I found that, with one form on top of the other, one saw bits of characters
in both StringGrids. As per a recommendation from ChatGPT, I turned on double buffering
for the MainForm, using the Object Inspector. This fixed the problem.

MCD note: I did not see this so I didn't use the double buffering.
*/

// Only needed if Active Engine List is Visible
if(!ActEngLst->Visible) return;

// CKR - make sure the headers are written
ActEngLst->Rows[ENG_LABEL_ROW]->CommaText = ENGDAT_TITLE;

// CKR - clear the active engine list

ActEngLst->RowCount = ENGINEMAX + 2;  // MCD reconstitute the Active Eng List
for (int i = ENG_TOPROW; i < ActEngLst->RowCount; i++) {
    ActEngLst->Rows[i]->CommaText = "*, *";
    }

// Scan EngineData list looking for active engines MCD Rewrote
int ActEngCount = 0;        // Active Eng Count
int ActEng = ENG_TOPROW;
for (int EngNum = ENG_TOPROW; EngNum < EngineData->RowCount ; EngNum++) {
	if(EngineData->Cells[ENG_STATE][EngNum] == ACTIVE) {
		ActEngLst->Rows[ActEng++]->Assign(EngineData->Rows[EngNum]);
        ActEngCount++;  // Increment Active Eng Count
        if (EngNum == EngineData->Row) {
            ActEngLst->Row = ActEngCount;
            }
   		}
    }
ActEngLst->RowCount = ActEngCount + 1;  // resize the Active Eng List (# active engines + title row)
ActEngUpDown->Max = ActEngCount;
ActEngUpDown->Position = ActEngLst->RowCount - ActEngLst->Row;    // keep the UpDown control in sync with the Active Engine List

}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ActEngUpDownClick(TObject *Sender,
      TUDBtnType Button)
{
// A bug in BCB4.0 - when this routine is called, the value of Position has
// already changed. Correct operation in CB10.1 "use the OnClick event handler
//to respond to changes just before the value of Position is changed."
if (Button == Comctrls::btPrev) {
	if (ActEngLst->Row < (ActEngLst->RowCount - 1))
		ActEngLst->Row = ActEngLst->RowCount - ActEngUpDown->Position + UPDOWNBUGFIX;
	}
if (Button == Comctrls::btNext) {
	if (ActEngLst->Row > 1 )
		ActEngLst->Row = ActEngLst->RowCount - ActEngUpDown->Position - UPDOWNBUGFIX;
	}
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ActEngLstClick(TObject *Sender)
{
//
if (ActEngLst->Row == -1 || ActEngLst->Col == -1) {
	ActEngLst->Row = ENG_TOPROW;  	// ignore out of bounds (if possible)
    ActEngUpDown->Position = ActEngLst->RowCount - ENG_TOPROW /* 1 */;
    }
//
ActEngUpDown->Position = ActEngLst->RowCount - ActEngLst->Row;    // keep the UpDown control in sync with the Active Engine List

// CKR - get the equivalent row in the EngineData list
if(ActEngLst->Cells[ENG_NAME][ActEngLst->Row] == "*") {           //CKR - if no engine name, just return
	return;
	}

String EngNum = ActEngLst->Cells[ENG_NUM][ActEngLst->Row];      //CKR - get the engine number, MCD rewrote for efficiency
if (ActEngLst->Row > 0) {
    EngineData->Row = EngNum.ToInt();
    }

EngineDataClick(Sender); //CKR Pretend that the EngineData list has been clicked
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ActEngLstDblClick(TObject *Sender)
{
//
// Startup the Engine or if field is empty, Edit Engine Name
if (!IsTIUConnected()){
	DebugMessagePopup("Warning", MainForm->ConnectMsg, "");
	return;
    }

// double clicking in column header or row header is ignored
if (ActEngLst->Col == ENG_NUM || ActEngLst->Row == ENG_LABEL_ROW) return;
    
// otherwise, startup the Engine
StartupButton->OnClick(StartupButton);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ActEngLstDrawCell(TObject *Sender, int ACol,
      int ARow, TRect &Rect, TGridDrawState State)
{

// CKR - I copied this from EngineDataDrawCell

// Draw the contents of each cell
ActEngLst->Canvas->Font->Size = 12;

ActEngLst->Canvas->Font->Style = ActEngLst->Canvas->Font->Style >> fsItalic;    //CKR - remove italics

// if the cell is selected, highlight it

if( State.Contains(gdSelected) ) {
	ActEngLst->Canvas->Font->Color = clWhite;
	ActEngLst->Canvas->Brush->Color = clBlue;		// White on Blue
	} else {
	//if (ptrOp[ARow] != NULL) {
    String EngNum = ActEngLst->Cells[ENG_NUM][ARow];
    if (ARow > 0 && ptrOp[EngNum.ToInt()] != NULL) {
		ActEngLst->Canvas->Font->Color = clBlack;	// Started up Engines are Black on Lime
		ActEngLst->Canvas->Brush->Color = clLime;
		} else {
		ActEngLst->Canvas->Font->Color = clBlack;	// Normal Black on White
		ActEngLst->Canvas->Brush->Color = clWhite;
		}
	}

// move a little to the right
Rect.Left = Rect.Left + LISTLEFTOFFSET;
// fill the rectangle
ActEngLst->Canvas->FillRect(Rect);
// draw the text
String text;
if ((ACol == ENG_NAME) && (ARow != ENG_LABEL_ROW) && (ARow != ENG_ALLROW) && (ActEngLst->Cells[ENG_NAME][ARow] != "*")) {
	text = ActEngLst->Cells[ACol][ARow] + "  TIU" + String(ActEngLst->Cells[TIU_NUMBER][ARow]);
	} else {
	text = ActEngLst->Cells[ACol][ARow];
	}
ActEngLst->Canvas->TextRect(Rect, Rect.Left, Rect.Top, text);
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ActEngLstKeyDown(TObject *Sender, WORD &Key,
      TShiftState Shift)
{
//    
// if ENTER is pressed, startup the Engine
if (Key == VK_RETURN) {
    int EngineNo = EngineData->Row;  // use engine number in spinner and its associated TIU number
	if (EngineData->Cells[ENG_NAME][EngineNo] != "*") {
		StartupButton->OnClick(MainForm->StartupButton);
    	}
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ActEngLstMouseDown(TObject *Sender,
      TMouseButton Button, TShiftState Shift, int X, int Y)
{
//
int ARow, ACol;
if (Button == mbRight || Button == mbMiddle) {
	ActEngLst->MouseToCell(X, Y, ACol, ARow);
    ActEngLst->Row = ARow;
	ActEngLst->Col = ACol;
    }

ActEngLstClick(Sender);
// Popup the PopupMenu
if (Button == mbRight) {
	ActEngLst->PopupMenu->Popup(Left + X, Top + Y);
    }
// Pop Out the Engine List
if (Button == mbMiddle) {
    PopOutEngineList->RefreshClick(NULL);
	PopOutEngineList->Visible = true;
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::ActEngChckBoxClick(TObject *Sender)
{
//
// Use Visible property which is slightly more efficent as the
// invisible box is not drawn until actually displayed
if(ActEngChckBox->Checked) {
	ActEngLst->Visible = true;
	ActEngUpDown->Visible = true;
	EngineData->Visible = false;
	EngineUpDown->Visible = false;
	FillActEngLst();            	// MCD  fill the active engine list
    if (ActEngLst->Row != TIUSpin->ValueAsInt) {
        ActEngLstClick(Sender);
        }
    }  else {
	EngineData->Visible = true;
	EngineUpDown->Visible = true;
	ActEngLst->Visible = false;
	ActEngUpDown->Visible = false;
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::LoadConfig()
{
//
// Process the Configuration Script
//
Setup->RFIDDetectorCount = -1;
Setup->RFIDReaderCount = -1;
Setup->SignalCount = -1;
// Lua State Pointer
M = NULL;
if (M != NULL) {
	lua_close(M);
    M = NULL;
    }

// initialization
AnsiString ConfigFileName = String(cStartDir) + "\\" + "config.lua";

if (FileExists(ConfigFileName)) {
    M = luaL_newstate();
    if (M == NULL) {
        if (DEBUGGING) DB->Debug_Print("Cannot create state: not enough memory");
        return;
        }
    //
    // Note: not all libraries are needed for the configuration script processing.
    // It might be possible to use the method described in Chapter 33, pg 358 of the lua
    // book to only load a the basic libraries. This would put the processing of
    // the script in a sandbox and make implementation safer.
    //
    luaL_openlibs(M);
    // Add the WorkingDir at the end of the Lua package path
    {
    AnsiString PathString = "package.path = package.path .. [[;" + WorkingDir + "?.lua]]";
    boolean bretv = luaL_dostring(M, PathString.c_str());
    assert(!bretv);	// fail on error
    }
    // compile and execute file
    ConfigurationSuccess = false;

    int load_stat = luaL_loadfile(M, ConfigFileName.c_str());	// load the chunk
    // Process the Configuration file if it is there
    if (load_stat == LUA_OK) {
        // Config file found, now run it
        int retv = lua_pcall(M, 0, 0, 0);	// run the chunk
        if (retv != LUA_OK) {	// LUA_ERRRUN, LUA_ERRMEM, LUA_ERRERR, LUA_ERRGCMM
            // Chunk did not run
            ShowMessage("Error " + String(retv) + " : " + String(lua_tostring(M, -1)));
			if (M != NULL) {
                lua_close(M);
                M = NULL;
                }
            }
    //
    // Load Configuration values
    //
        if (retv == LUA_OK) {
            ConfigurationSuccess = true;
            if (lua_getglobal(MainForm->M, "Engine_List_Title") == LUA_TSTRING) {
                Engine_List_Label->Caption = lua_tolstring(MainForm->M, -1, NULL);	// Engine List Title
                }
            lua_pop(M,1);	// remove global value
            if (lua_getglobal(MainForm->M, "Working_Directory") == LUA_TSTRING) {
                WorkingDir = lua_tolstring(MainForm->M, -1, NULL);					// Working Directory
                }
            lua_pop(M,1);	// remove global value
            if (lua_getglobal(MainForm->M, "ConnectMsg") == LUA_TSTRING) {
                ConnectMsg = lua_tolstring(MainForm->M, -1, NULL);					// ConnectMsg
                }
            lua_pop(M,1);	// remove global value
            if (lua_getglobal(MainForm->M, "DisconnectMsg") == LUA_TSTRING) {
                DisconnectMsg = lua_tolstring(MainForm->M, -1, NULL);				// DisconnectMsg
                }
            lua_pop(M,1);	// remove global value

            assert(Setup);	// Setup window was constructed above

            //
            // load expected number of RFID detectors and Signals
            //
            if (lua_getglobal(MainForm->M, "RFIDDetectorCount") == LUA_TNUMBER) {
                Setup->RFIDDetectorCount = lua_tonumber(MainForm->M, -1);	// Number of expected RFID Detectors
                }
            lua_pop(M,1);	// remove global

            if (lua_getglobal(MainForm->M, "RFIDReaderCount") == LUA_TNUMBER) {
                Setup->RFIDReaderCount = lua_tonumber(MainForm->M, -1);	    // Number of expected RFID Readers
                }
            lua_pop(M,1);	// remove global

            if (lua_getglobal(MainForm->M, "SignalCount") == LUA_TNUMBER) {
                Setup->SignalCount = lua_tonumber(MainForm->M, -1);	        // Number of expected Signals
                }
            lua_pop(M,1);	// remove global

            //
            // Load Configuration Variables into Setup Variables
            //
            if (lua_getglobal(MainForm->M, "TIU") == LUA_TNUMBER) {
                TIUSpin->ValueAsInt = lua_tonumber(MainForm->M, -1);	// TIU Number
                }
            lua_pop(M,1);	// remove global

            if (lua_getglobal(MainForm->M, "Remote") == LUA_TNUMBER) {
                RemoteSpin->Value = lua_tonumber(MainForm->M, -1);		// Remote Number
                }
            lua_pop(M,1);	// remove global

            if (lua_getglobal(MainForm->M, "Engine") == LUA_TNUMBER) {
                EngineSpin->Value = lua_tonumber(MainForm->M, -1);		// Engine Number
                }
            lua_pop(M,1);	// remove global

            #define CONFIG_SETUP_VALUE(VAR) \
                if (lua_getglobal(MainForm->M, #VAR) == LUA_TNUMBER) { \
                    Setup->VAR->Value = lua_tonumber(MainForm->M, -1); \
                    } \
                lua_pop(M,1);	// remove global

            CONFIG_SETUP_VALUE(RadioCOMPortNum);	// Radio Comm Port
            CONFIG_SETUP_VALUE(RFIDCOMPortNum);		// RFID Comm Port
            CONFIG_SETUP_VALUE(MaxSpeed);		    // Maximum Speed
            CONFIG_SETUP_VALUE(Quick_Ctrl_Scaling);	// Quick Ctrl Scaling
            CONFIG_SETUP_VALUE(TransparentSpin);	// Transparency
            CONFIG_SETUP_VALUE(MPHScaling);			// Speed Scaling
            CONFIG_SETUP_VALUE(NumRetries);			// Number of Retries

            #define CONFIG_SETUP_CHECKBOX(VAR) \
                if (lua_getglobal(MainForm->M, #VAR) == LUA_TBOOLEAN) { \
                    Setup->VAR->Checked = lua_toboolean(MainForm->M, -1);  \
                    } \
                lua_pop(M,1);	// remove global
            
            CONFIG_SETUP_CHECKBOX(AutoConnect); 	// Auto Connect to Comm Ports and TIU - true or false
            //
            CONFIG_SETUP_CHECKBOX(AIU_WiFi_Mode);	    // AIU WiFi enabled - true or false
            CONFIG_SETUP_CHECKBOX(Signals_WiFi_Mode);	// Signals WiFi enabled - true or false
            //
            CONFIG_SETUP_CHECKBOX(RFID_COM_Mode);	// RFID Serial COM enabled - true or false
            CONFIG_SETUP_CHECKBOX(RFID_WiFi_Mode);	// RFID WiFi enabled - true or false
            //
            CONFIG_SETUP_CHECKBOX(Radio_COM_Mode);	// Radio Serial COM enabled - true or false
            CONFIG_SETUP_CHECKBOX(WTIU_WiFi_Mode);	// WTIU WiFi enabled - true or false
            //
            CONFIG_SETUP_CHECKBOX(AutoCoupler);		// Auto Coupler Slack Sound
            CONFIG_SETUP_CHECKBOX(AutoBell);	    // Auto Bell Sounds
            CONFIG_SETUP_CHECKBOX(AutoWhistle);	    // Auto Whistle Sounds
            CONFIG_SETUP_CHECKBOX(ShortBellWhistleSequence);		//	Short Bell/Whistle Sequence
            CONFIG_SETUP_CHECKBOX(ExtendedSUSD);	// Extended Startup and Shutdown Sounds
            CONFIG_SETUP_CHECKBOX(StandardAccDec);	// Use Acc/Dec = 1
            CONFIG_SETUP_CHECKBOX(DisableEmergencyStopButton);		// Disable Emergency Stop
            CONFIG_SETUP_CHECKBOX(AutoHotButtons);		// Auto Hot Buttons
            CONFIG_SETUP_CHECKBOX(AutoQuickControls);	// Auto Quick Controls
            CONFIG_SETUP_CHECKBOX(AutoSoundCtrls);		// Auto Sound Ctrls
            CONFIG_SETUP_CHECKBOX(SmokeOFFatStartup);	// Smoke off at Engine Startup
            CONFIG_SETUP_CHECKBOX(EncodedRecordFormat);	// Encoded Record Format
            CONFIG_SETUP_CHECKBOX(RepeatPlayback);		// Repeat Playback
            CONFIG_SETUP_CHECKBOX(Safety_Mode);			// enables double switch throws, whistle off and bell off in safety mode
            CONFIG_SETUP_CHECKBOX(AutoFindEngines);		// Automatically do a [Read] command after a successful connect
            CONFIG_SETUP_CHECKBOX(GeneralSounds);		// Allow RTC to generate sound effects
            CONFIG_SETUP_CHECKBOX(NoSleep);		        // Prevent PC from going to Sleep

            #define CONFIG_SETUP_BOOLEAN(VAR) \
                if (lua_getglobal(MainForm->M, #VAR) == LUA_TBOOLEAN) { \
                    Setup->VAR = lua_toboolean(MainForm->M, -1);  \
                    } \
                lua_pop(M,1);	// remove global

            CONFIG_SETUP_BOOLEAN(Show_Pause_Button);	// Show Pause Button on Program Control window
            CONFIG_SETUP_BOOLEAN(Comm_Not_Required);	// enables some windows even without communications - use with care

            #define CONFIG_SETUP_STRING(VAR) \
                if (lua_getglobal(MainForm->M, #VAR) == LUA_TSTRING) { \
                    Setup->VAR = lua_tostring(MainForm->M, -1);  \
                    } \
                lua_pop(M,1);	// remove global

            CONFIG_SETUP_STRING(SwitchStraight);	// word used for Switch when in the Normal position
            CONFIG_SETUP_STRING(SwitchDiverge);		// word used for Switch when in the Reverse position

             #define CONFIG_SETUP_TEXT(VAR) \
                if (lua_getglobal(MainForm->M, #VAR) == LUA_TBOOLEAN) { \
                    Setup->VAR->Text = lua_tostring(MainForm->M, -1);  \
                    } \
                lua_pop(M,1);	// remove global
            }
        } else {
        // Config file not found - if we are debugging, then show a message otherwise continue on without one
        if (DEBUGGING) DB->Debug_Print("Error " + String(load_stat) + " : " + String(lua_tostring(M, -1)));
        if (M != NULL) {
            lua_close(M);
            M = NULL;
            }
        }
    }
}
//---------------------------------------------------------------------------


