#pragma warning(disable : 4706)   // assignment within conditional expression
#pragma warning(disable : 4996)	  // _CRT_SECURE_NO_DEPRECATE

#include "stdafx.h"
#include <Windows.h>
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <timeapi.h>
#include "SerialPort\SerialPort.h"
#include "AVRInterface.h"
//#include "AVRCommandDecoder.h"
#include "ConsoleHandler.h"

enum {
	COM_NO_PORT = -1,
	COM_MIN_PORT = 1,
	COM_MAX_PORT = 99
};
CSerialPort rs232Port;
int avrOnPort = COM_NO_PORT;	// Serial Port 1, 2, ... taking note that some USB adapters can be up toward channel 11, 12, ...
unsigned avrBaud = 9600;

bool verboseMode = false;
const uint32_t retryInterval = 1000;	// ms

AVRInterface *avr;

#if 0
// Each DT is the hex-character from the stream
//
//
typedef struct {
	uint8_t DT0;	// * Baud Rate		 '@'
	uint8_t DT1;	// * Receive Buffer  'E'
	uint8_t DT2;	// * Receive Buffer  '0'
	uint8_t DT3;	// *                 '1'
	uint8_t DT4;	// * Command Timeout '9'
	uint8_t DT5;	// *                 '0'
	uint8_t DT6;	// *                 '0'
	uint8_t DT7;	// * System          '0':Ok, '1':Busy
	uint8_t DT8;	// * Power            0:Off, 1:On
	uint8_t DT9;	// Input      0: Phono, 1:CD, 2:Tuner, 3:CD-R, 4:MD-Tape, 5:DVD, 6:D-TV, 7:Cbl, 9:VCR1, A:VCR2
	uint8_t DT10;	// 6ch input 0:Off, 1:On
	uint8_t DT11;	// Input Mode 0:AUTO, 2:DTS, 4:Analog, 5:Analog Only
	uint8_t DT12;	// Audio Mute 0:Off, 1:On
	uint8_t DT13;	// Zone2 Input 0: PHONO / 1: CD / 2: TUNER / 3: CD-R / 4: MD-TAPE / 5: DVD / 6: D-TV-LD / 7: CBL-SAT / 9: VCR1 / A: VCR2-DVR / C: V-AUX
	uint8_t DT14;	// Zone2 Mute 0: OFF / 1: ON
	uint8_t DT15;	// Master Volume Upper 4 bit
	uint8_t DT16;	// Master Volume Lower 4 bit
	uint8_t DT17;	// Zone2 Volume Upper 4 bit
	uint8_t DT18;	// Zone2 Volume Lower 4 bit
	uint8_t DT19;	// Program Upper 4 bit
	uint8_t DT20;	// Program Lower 4 bit
	uint8_t DT21;	// Effect 0: OFF / 1: ON
	uint8_t DT22;	// 6.1/ES key status 0: OFF / 1: MATRIX ON / 2: DISCRETE ON / 3: AUTO
	uint8_t DT23;	// OSD* 0: FULL / 1: SHORT / 2: OFF
	uint8_t DT24;	// Sleep 0: 120 / 2: 90 / 3: 60 / 4: 30 / 5: OFF
	uint8_t DT25;	// Tuner Page 0: Page A / 1: Page B / 2: Page C / 3: Page D / 4: PageE
	uint8_t DT26;	// Tuner No. 0: No.1 / 1: No.2 / 2: No.3 / 3: No.4 / 4: No.5 / 5: No.6 / 6: No.7 / 7: No.8
	uint8_t DT27;	// Night mode 0: OFF / 1: ON
	uint8_t DT28;	// Care
	uint8_t DT29;	// Speaker relay A 0: OFF / 1: ON
	uint8_t DT30;	// Speaker relay B 0: OFF / 1: ON
	uint8_t DT31;	// Playback 0: 6ch input / 1: Analog / 2: PCM / 3: DD*(except 2.0) / 4: DD(2.0) / 5: DD.Karaoke / 6: DD.EX / 7: DTS / 8: DTS-ES / 9: Other DIGITAL / A: DTS Analog Mute / B: DTS ES Discrete
	uint8_t DT32;	// Fs 0: Analog / 1: 32kHz / 2: 44.1kHz / 3: 48kiHz / 4: 64kHz / 5: 88.2kHz / 6: 96kHz / 7: Unknown B: DTS 96/24
	uint8_t DT33;	// EX/ES playback 0: OFF / 1: MATRIX ON / 2: DISCRETE ON
	uint8_t DT34;	// Thr / Bypass 0: Normal / 1: Bypass
	uint8_t DT35;	// RED dts 0: Release / 1: Wait
	uint8_t DT36;	// Head Phone 0: OFF / 1: ON
	uint8_t DT37;	// TUNER BAND 0: FM / 1: AM
	uint8_t DT38;	// TUNER TUNED 0: NOT TUNED / 1: TUNED
	uint8_t DT39;	// DC1 Control Out 0: LOW / 1: HIGH

	uint8_t DT40;	 // Don’t care
	uint8_t DT41;	 // Don't Care
	uint8_t DT42;	 // 0-2 DC1 TRG Ctrl. 0: Zone1 / 1: Zone2 / 2: Zone1&2
	uint8_t DT43;	 // 0/1 dts 96/24 0: OFF / 1: ON
	uint8_t DT44;	 // 0-2 DC2 TRG Ctrl. 0: Zone1 / 1: Zone2 / 2: Zone1&2
	uint8_t DT45;	 // 0/1 DC2 Trigger 0: LOW / 1: HIGH
	uint8_t DT46;	 // SP B set 0: Zone1 / 1: Zone2
	uint8_t DT47;	 // Zone 2 SP out 0: OFF / 1: ON
	uint8_t DT48;	 // MAIN R Upper 4bit
	uint8_t DT49;	 // Lower 4bit
	uint8_t DT50;	 // MAIN L Upper 4bit
	uint8_t DT51;	 // Lower 4bit
	uint8_t DT52;	 // CENTER Upper 4bit
	uint8_t DT53;	 // Lower 4bit
	uint8_t DT54;	 // REAR R Upper 4bit
	uint8_t DT55;	 // Lower 4bit
	uint8_t DT56;	 // REAR L Upper 4bit
	uint8_t DT57;	 // Lower 4bit
	uint8_t DT58;	 // SUR BACK Upper 4bit
	uint8_t DT59;	 // R Lower 4bit
	uint8_t DT60;	 // SUR BACK Upper 4bit
	uint8_t DT61;	 // L Lower 4bit
	uint8_t DT62;	 // FRONT R Upper 4bit
	uint8_t DT63;	 // Lower 4bit
	uint8_t DT64;	 // FRONT L Upper 4bit
	uint8_t DT65;	 // Lower 4bit
	uint8_t DT66;	 // SWFR 1 Upper 4bit
	uint8_t DT67;	 // Lower 4bit
	uint8_t DT68;	 // Don't Care
	uint8_t DT69;	 // Don't Care
	uint8_t DT70;	 // Don't Care
	uint8_t DT71;	 // Don't Care
	uint8_t DT72;	 // Don't Care
	uint8_t DT73;	 // Don't Care
	uint8_t DT74;	 // LFE Lvl. SP Upper 4bit
	uint8_t DT75;	 // Lower 4bit
	uint8_t DT76;	 // LFE Lvl. HP Upper 4bit
	uint8_t DT77;	 // Lower 4bit
	uint8_t DT78;	 // Audio Delay Upper 4bit
	uint8_t DT79;	 // Lower 4bit

	uint8_t DT80;	// Don't Care
	uint8_t DT81;	// Don't Care
	uint8_t DT82;	// Don't Care
	uint8_t DT83;	// Don't Care
	uint8_t DT84;	// Input mode set 0: AUTO / 1: LAST
	uint8_t DT85;	// Dimmer 0: -4 / 1: -3 / 2: -2 / 3: -1 / 4: 0
	uint8_t DT86;	// OSD Message
	uint8_t DT87;	// OSD shift Upper 4bit
	uint8_t DT88;	// Lower 4bit
	uint8_t DT89;	// Glay back 0: OFF / 1: AUTO
	uint8_t DT90;	// Video conversion 0: OFF / 1: ON
	uint8_t DT91;	// D. Range SP 0: MAX / 1: STD / 2: MIN
	uint8_t DT92;	// HP 0: MAX / 1: STD / 2: MIN
	uint8_t DT93;	// Zone 2 vol. Out
	uint8_t DT94;	// Don't Care
	uint8_t DT95;	// Memory guard 0: OFF / 1: ON
	uint8_t DT96;	// SP set Center 0: Large / 1: Small / 2: None
	uint8_t DT97;	// Main 0: Large / 1: Small
	uint8_t DT98;	// Rear L/R 0: Large / 1: Small / 2: None
	uint8_t DT99;	// Rear CT 0: Large / 1: Small / 2: None
	uint8_t DT100;	// Front 0: Yes / 1: None
	uint8_t DT101;	// LFE/BASS 0: SWFR / 1: Main / 2: Both
	uint8_t DT102;	// 6CH Center 0: Center / 1: Main
	uint8_t DT103;	// SWFR 0: SWFR / 1: Main
	uint8_t DT104;	// Main level 0: Normal / 1: -10dB
	uint8_t DT105;	// Test mode 0: OFF / 1: Dolby / 2: DTS
	uint8_t DT106;	// Don't Care
	uint8_t DT107;	// LVL 6CH MAIN L Upper 4bit
	uint8_t DT108;	// Lower 4bit
	uint8_t DT109;	// MAIN R Upper 4bit
	uint8_t DT110;	// Lower 4bit
	uint8_t DT111;	// CENTER Upper 4bit
	uint8_t DT112;	// Lower 4bit
	uint8_t DT113;	// SL Upper 4bit
	uint8_t DT114;	// Lower 4bit
	uint8_t DT115;	// SR Upper 4bit
	uint8_t DT116;	// Lower 4bit
	uint8_t DT117;	// SBL Upper 4bit
	uint8_t DT118;	// Lower 4bit
	uint8_t DT119;	// SBR Upper 4bit
	uint8_t DT120;	// Lower 4bit
	uint8_t DT121;	// FRONT L Upper 4bit
	uint8_t DT122;	// Lower 4bit
	uint8_t DT123;	// FRONT R Upper 4bit
	uint8_t DT124;	// Lower 4bit
	uint8_t DT125;	// SWFR Upper 4bit
	uint8_t DT126;	// Lower 4bit
	uint8_t DT127;	// 0 - C Z3 Input
	uint8_t DT128;	// 0/1 Z3 Mute
	uint8_t DT129;	// 0 - F Z3 Volume Upper 4bit

	uint8_t DT130;	// 0 - F Lower 4bit
	uint8_t DT131;	// Don't Care
	uint8_t DT132;	// MULTI_CH SELECT 00:6CH / 01:8CH TUNER / 02: 8CH CD / 04: 8CH CD-R / 05: 8CH DVD / 06: DTV / 07: 8CH CBL/SAT / 09: 8CH VCR1 / 0A: VCR2/DVR / 0C: VAUX
	uint8_t DT133;	// MULTI_CH SURROUND to 00: Surround / 01: Main
	uint8_t DT134;	// SP SET SW1 00: L-R / 01: F-R / 02: NONE
	uint8_t DT135;	// SP SET CROSSOVER 00: 40Hz / 01: 60Hz / 02: 80Hz / 03: 90Hz / 04: 100Hz / 05: 110Hz / 06: 120Hz / 07: 160Hz / 08: 200Hz
	uint8_t DT136;	// COMPONENT OSD 00: OFF / 01: ON
	uint8_t DT137;	// PB/SB SELECT 00: PR / 01: SB

	uint8_t DT138[100];	// From here on is just buffer in case it sends more data
} AVR_Configuration_T;



typedef struct {
	uint8_t type[5];	// Model ID
	uint8_t version;	// A-Z
	uint8_t length[2];	// 1 - 255
} AVR_StatusHeader_T;

typedef struct {
	bool headerValid;
	bool configValid;
	AVR_StatusHeader_T header;
	AVR_Configuration_T config;
	
} AVR_Status_T;

//AVR_StatusHeader_T avrStatusHeader;
//AVR_Configuration_T avrConfigData;
AVR_Status_T avrStatus;
#endif

typedef enum {
	EXIT_OK = 0,
	EXIT_IllegalOption,
	EXIT_InternalSanityCheckFail,
	EXIT_ExternalQuit,
	EXIT_AbnormalError,
} ExitCode;

constexpr auto MAXTEXTLEN = 512;
char progname[MAXTEXTLEN];
char ininame[MAXTEXTLEN] = "";


int AttachToSerialPort();
int DetachSerialPort();

// SerialSend
//
// Try to send the message.
//
// @param[in] p is a pointer to the message to send
// @param[in] len is the count of bytes in the message
// @returns true if the serial interface accepted it.
//
bool SerialSend(const uint8_t *message, uint32_t len);

// Just big enough to hold an OSD message which is a 1 message command, 4 messages with text
#define SERIALQUEUESIZE 5
typedef struct {
	uint8_t *message;
	uint16_t len;
} SerialQueue_T;

static SerialQueue_T serialQueue[SERIALQUEUESIZE];
static int serialQueueCount = 0;


// SendMessageToSerial
// 
// If there are parameters passed, insert a message into the queue.
// Process the queue (with zero or more messages) to send
//
// @param[in] p is a pointer to the message to send
// @param[in] len is the count of bytes in the message
// @returns false if the queue (which is a fixed size) is full.
//
//bool SendMessageToSerial(const uint8_t *p = NULL, uint16_t len = 0);


// ProcessSerialReceive
//
// See if anything came in, and if so, process it.
//
// @returns true if a message was processed.
//
SerialQueue_T ProcessSerialReceive(void);
//void HandleMessage(uint8_t *szBuffer, uint32_t num);

void EnumerateComPorts();
unsigned long Hex2Dec(uint8_t *p, int dig);

//void ShowAllStatusInfo();
void GetAndSendCustomMessage();
void GetAndSendExtendedMessage();
void GetAndSendOSDMessage();
bool SendExtendedMessage(char *buf);

void EmitRuntimeHelp();
DWORD AppTime();

bool UserExitRequested = false;
bool SystemExitRequested = false;

typedef struct {
	uint8_t rCmd;
	uint8_t configOffset;	// offset into the Config DT0 - DT155 array, or 0xFF for no action
	uint8_t numToTransfer;	// number of bytes to transfer into the Config array, or 0 for no action
	bool showAll;			// true to call ShowAllStatusInfo after processing
	const char *TextFormatter;	// used primarily for development printf(TextFormatter, value)
	const char *(*fncValueToText)(uint8_t rDat);
} MessageHandler_T;


int CS_KeyHit() {
	int h = _kbhit();
	return h;
}

int CS_GetChar() {
	int k = _getch();     // More leadin chars
	return k;
}

BOOL WINAPI ControlIntercept(DWORD CtrlType) {
	switch (CtrlType) {
	case CTRL_C_EVENT:
	case CTRL_CLOSE_EVENT:
		UserExitRequested = true;
		return FALSE;
		break;
	case CTRL_BREAK_EVENT:
	case CTRL_LOGOFF_EVENT:
	case CTRL_SHUTDOWN_EVENT:
		SystemExitRequested = true;
		return FALSE;
	default:
		return FALSE;
	}
}


void ProcessWindowsMessage(void) {
	MSG msgx;
	bool msgReturn;

	msgReturn = PeekMessage(&msgx, NULL, WM_QUIT, WM_QUIT, PM_NOREMOVE);
	if (msgReturn == true && msgx.message == WM_QUIT) {
		SystemExitRequested = true;
	}
}


bool SerialSend(const uint8_t *message, uint32_t len) {
	bool retVal = false;

	if (rs232Port.Write((const LPVOID)message, len) == len) {
		retVal = true;
	} else {
		Console_WriteAt(0, -1, "***** Failed to send. Port not open?");
	}
	return retVal;
}


void EmitSpinner() {
	static int x = 0;
	const char arrow[] = "|/-\\|/-\\";

	printf("%c\b", arrow[x++]);
	if (x == 8) x = 0;
}

typedef struct {
	const char *pMsg;
	uint16_t MsgLen;
} Message_T;

typedef struct {
	const char Char;
	const char *pDescription;
	Message_T TxMsg;
} UserCmd_T;

// Runtime commands from the keyboard to send to the AVR
//
// [ESC]
//   [1!] [2@] [3#]  4    5    6    7    8    9    0    -    =    
//    qQ   wW   eE   r[R] t[T] [yY] [uU] [iI] o[O] [pP] [{   ]}    \|
//     aA   [sS] dD   f[F] gG   hH   jJ   kK   lL   ;[:] '"
//      zZ   xX   cC   [vV] bB   nN   [mM] ,<   .>   [/?]
//
const UserCmd_T UserCommands[] = {
	{ 'R', "Ready",				{"\x11" "000" "\x03", 5 } },
	{ 'P', "Power On",	 },
	{ 'p', "Power Off",	 },
	{ '1', "Zone 1 On",			{"\x02" "07E7E" "\x03", 7} },
	{ '!', "Zone 1 Standby",	{"\x02" "07E7F" "\x03", 7} },
	{ '2', "Zone 2 On",			{"\x02" "07EBA" "\x03", 7} },
	{ '@', "Zone 2 Standby",	{"\x02" "07EBB" "\x03", 7} },
	{ '3', "Zone 3 On",			{"\x02" "07AED" "\x03", 7} },
	{ '#', "Zone 3 Standby",	{"\x02" "07AEE" "\x03", 7} },
	{ 'V', "Vol 1 Up",			{"\x02" "07A1A" "\x03", 7} },
	{ 'v', "Vol 1 Down",		{"\x02" "07A1B" "\x03", 7} },
	{ 'M', "Vol 1 Mute",		{"\x02" "07EA2" "\x03", 7} },
	{ 'm', "Vol 1 UnMute",		{"\x02" "07EA3" "\x03", 7} },
	{ 'O', "OSD Test Message",  },
	{ 'T', "Tuning freq rqst",	{"\x02" "22000" "\x03", 7} },
	{ 'Y', "Zone 1 Vol rqst",	{"\x02" "22001" "\x03", 7} },
	{ 'y', "Zone 2 Vol rqst",	{"\x02" "22002" "\x03", 7} },
	{ 'U', "Zone 1 Input rqst",	{"\x02" "22003" "\x03", 7} },
	{ 'u', "Zone 2 Input rqst",	{"\x02" "22004" "\x03", 7} },
	{ 'I', "Zone 3 Vol rqst",	{"\x02" "22005" "\x03", 7} },
	{ 'i', "Zone 3 Input rqst",	{"\x02" "22006" "\x03", 7} },
	{ 'F', "Factory Reset",		{"\x13" "\x7F\x7F\x7F" "\x03", 5} },

	//{ 'J', "Dual Mono - Main",	{"\x02" "07E93" "\x03", 7} },
	//{ 'K', "Dual Mono - Sub",	{"\x02" "07E94" "\x03", 7} },
	//{ 'L', "Dual Mono - All",	{"\x02" "07E95" "\x03", 7} },
	{ ':', "Send Command Message" },
	{ '=', "Send Extended Message" },
	{ '/', "Show All" },
	{ '?', "Help on runtime commands" },
	{ 'S', "Set RTS" },
	{ 's', "Clear RTS" },
	{ '\x1B', "Quit App" },
};

void ProcessKeyboard(void) {
	static uint32_t spin = 0;
	bool cmdFound = false;

	Console_SetCursor(0, -1);
	if (spin++ % 16 == 0)
		EmitSpinner();
	if (CS_KeyHit()) {
		int c = CS_GetChar();
		switch (c) {
		case 'P':
			avr->AVRCommand(AVRSubsystem_T::subMain, AVRFunction_T::fncPower, AVRArg_T::eOn);
			break;
		case 'p':
			avr->AVRCommand(AVRSubsystem_T::subMain, AVRFunction_T::fncPower, AVRArg_T::eOff);
			break;
		case 'O':
			GetAndSendOSDMessage();
			break;
		case ':':
			GetAndSendCustomMessage();
			break;
		case '=':
			GetAndSendExtendedMessage();
			break;
		case '?':
			EmitRuntimeHelp();
			break;
		case '/':
			avr->ReportAllStatus();
			break;
		case '\x1B':
			UserExitRequested = true;
			break;
		case 'S':
			rs232Port.Set_RTS_State(TRUE);
			Console_SetCursor(0, -1);
			printf("RTS set ON");
			Console_ScrollBottomRegion();
			break;
		case 's':
			rs232Port.Set_RTS_State(FALSE);
			Console_SetCursor(0, -1);
			printf("RTS set OFF");
			Console_ScrollBottomRegion();
			break;
		default:
			for (int i = 0; i < sizeof(UserCommands) / sizeof(UserCmd_T); i++) {
				if (UserCommands[i].Char == c) {
					//SendMessageToSerial((const uint8_t *)UserCommands[i].TxMsg.pMsg, UserCommands[i].TxMsg.MsgLen);
					avr->SendMessageToSerial(UserCommands[i].TxMsg.pMsg, UserCommands[i].TxMsg.MsgLen);
					cmdFound = true;
					break;
				}
			}
			if (!cmdFound) {
				Console_SetCursor(0, -1);
				printf("Unrecognized command '%c' (0x%02X)", isprint(c) ? c : '.', (unsigned char)c);
				Console_ScrollBottomRegion();
			}
			break;
		}
	}
	return;
}


void UserCommandsSanityCheck() {
	uint8_t usedKey[256] = { 0 };
	bool fail = false;

	size_t numCommands = sizeof(UserCommands) / sizeof(UserCmd_T);
	for (size_t i = 0; i < numCommands; i++) {
		usedKey[UserCommands[i].Char]++;
		if (usedKey[UserCommands[i].Char] > 1) {
			printf("UserCommandsSanityCheck: Command key %c used more than once\n", UserCommands[i].Char);
			fail = true;
		}
	}
	if (fail) {
		printf("***** UserCommandsSanityCheck: Errors found in UserCommands table\n");
		exit(EXIT_InternalSanityCheckFail);
	}
}

void EmitRuntimeHelp() {
	for (int i = 0; i < sizeof(UserCommands) / sizeof(UserCmd_T); i++) {
		char tinyBuf[6];
		if (isprint(UserCommands[i].Char)) {
			sprintf_s(tinyBuf, sizeof(tinyBuf), "%c", UserCommands[i].Char);
		} else if (UserCommands[i].Char == '\x1B') {
			sprintf_s(tinyBuf, sizeof(tinyBuf), "%s", "<esc>");
		} else if (UserCommands[i].Char < ' ') {
			sprintf_s(tinyBuf, sizeof(tinyBuf), "^%c", UserCommands[i].Char + 64);
		} else {
			sprintf_s(tinyBuf, sizeof(tinyBuf), "0x%02X", (unsigned char)UserCommands[i].Char);
		}
		char buf[MAXTEXTLEN];
		sprintf_s(buf, MAXTEXTLEN, "  %5s  %-25s", tinyBuf, UserCommands[i].pDescription);
		Console_AdvanceToNextLineIfNotRoomFor((short)strlen(buf), 1);
		Console_Write(buf);
	}
	if (rs232Port.IsOpen()) {
		Console_ScrollBottomRegion();
		Console_SetCursor(0, -1);
		printf("  Com Status: RTS: %-3s ", rs232Port.Get_RTS_State() ? "ON" : "OFF");
		printf("DTR: %-3s ", rs232Port.Get_DTR_State() ? "ON" : "OFF");
		printf("CTS: %-3s ", rs232Port.Get_CTS_State() ? "ON" : "OFF");
		printf("DSR: %-3s ", rs232Port.Get_DSR_State() ? "ON" : "OFF");
		printf("RI: %-3s", rs232Port.Get_RI_State() ? "ON" : "OFF");
		Console_ScrollBottomRegion();
		Console_SetCursor(0, -1);
	}
}

void EmitBuffer(const char *prefix, const uint8_t *buf, size_t len = 0, bool appendReturn = false) {
	int i = 0;
	const char *p = (const char *)buf;
	char txtBuf[MAXTEXTLEN] = "";

	if (len == 0) len = strlen((const char *)buf);
	sprintf_s(txtBuf, MAXTEXTLEN, "%7.3f: [%3d]%s", (float)(AppTime())/1000.0, (int)strlen(p), prefix);
	Console_Write(txtBuf);
	while (*p && ((unsigned)(p - (const char *)buf) < len)) {
		if (isprint((char)*p)) {
			putchar(*p);
			i++;
		} else if (*p == '\r') {
			putchar('\n');
			Console_ScrollBottomRegion();
		} else if (*p == '\n') {
			// skip it
		} else {
			printf("[%02X]", (unsigned char)*p);
		}
		if ((i & 3) == 0) {
			if (Console_AdvanceToNextLineIfNotRoomFor(12, 1)) {
				//Console_ScrollBottomRegion();
				printf("                     ");	// sized to get past the timestamp, length, and prefix
				i = 0;
			} else {
				printf(" ");
			}
		}
		p++;
	}
	if (appendReturn) {
		Console_ScrollBottomRegion(); // putch('\r');
	} else {
		//putch('\n');
		//Console_ScrollBottomRegion();
	}
}

void HexUppercase(char *p) {
	const char *hex = "0123456789ABCDEF";
	while (*p) {
		if (*p >= 'a' && *p <= 'f')
			*p = *p - 'a' + 'A';
		if (NULL == strchr(hex, *p)) {
			*p = '\0';
			return;
		}
		p++;
	}
}

void GetAndSendOSDMessage() {
	char buf[MAXTEXTLEN];
	Console_ScrollBottomRegion();
	Console_ScrollBottomRegion();
	Console_WriteAt(0, -2, "Enter OSD string to send: |________________|\rEnter OSD string to send: |");
	gets_s(buf, sizeof(buf));
	if (strlen(buf) == 0) {
		return;
	} else if (strlen(buf) > 16) {
		Console_WriteAt(0, -1, "  Invalid. String must be 16 chars or less");
		Console_ScrollBottomRegion();
	} else {
		Console_ScrollBottomRegion();
		if (avr->AVRSendOSDMessage(buf)) {
			// success
		} else {
			Console_WriteAt(0, -1, "  Failed to send OSD message, Invalid chars perhaps.");
			Console_ScrollBottomRegion();
		}
	}
}

void GetAndSendCustomMessage() {
	char buf[20];
	char msg[30];
	Console_ScrollBottomRegion();
	Console_ScrollBottomRegion();
	Console_WriteAt(0, -2, "Enter Hex string to send: ");
	gets_s(buf, sizeof(buf));
	HexUppercase(buf);
	if (strlen(buf) != 5) {
		Console_WriteAt(0, -1, "  Invalid. String must be exactly 5 HEX chars");
		Console_ScrollBottomRegion();
	} else {
		Console_ScrollBottomRegion();
		sprintf_s(msg, sizeof(msg), "\x02%s\x03", buf);
		avr->SendMessageToSerial(msg, (uint16_t)strlen(msg));
	}
}

bool SendExtendedMessage(char * buf) {
	HexUppercase(buf);		// Gets nulled if there are non-Hex
	if (strlen(buf)) {
		char msg[75];		// Hope it is large enough
		const char * prefix = "\x14" "20";
		unsigned len = (unsigned)strlen(buf);
		sprintf_s(msg, 70, "%s%02X%s", prefix, len, buf);
		char *p = &msg[1];
		uint8_t checksum = 0;
		do {
			checksum += *p++;
		} while (*p && (p < msg + 70));	// 70 < 75 to generously reserve space for SUM0,SUM1,ETX,\0
		sprintf_s(p, 75 - strlen(msg), "%02X\x03", checksum);
		Console_ScrollBottomRegion();
		avr->SendMessageToSerial(msg, (uint16_t)strlen(msg));
		return true;
	}
	return false;
}

void GetAndSendExtendedMessage() {
	char buf[60];
	Console_ScrollBottomRegion();
	Console_ScrollBottomRegion();
	Console_WriteAt(0, -2, "Enter DT0...DTX Hex string to send: ");
	gets_s(buf, sizeof(buf) - 1);
	SendExtendedMessage(buf);
}


void GetProgName(char *name) {
	char *p = strrchr(name, '\\');
	if (p) {
		p++;
	} else {
		p = name;
	}
	strcpy_s(progname, MAXTEXTLEN, p);
	p = strrchr(progname, '.');
	if (p && 0 == stricmp(p, ".exe")) {
		*p = '\0';
	}
	strcpy_s(ininame, MAXTEXTLEN, progname);
	strcat_s(ininame, MAXTEXTLEN, ".ini");
}


bool InformationUpdate(AVRMessageType_T type, const void *message, uint32_t attrib) {
	const char *msg = (const char *)message;
	char buf[MAXTEXTLEN] = "";

	if (verboseMode) {
		switch (type) {
			case mtState:
				sprintf_s(buf, MAXTEXTLEN, "AVR Status: %-60s", avr->GetStateLabel(attrib));
				Console_SetColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY);
				Console_WriteAt(0, 1, buf);
				Console_RestoreColor();
				break;
			case mtInfo:
				Console_WriteAt(0, -1, msg);
				Console_ScrollBottomRegion();
				break;
			case mtModelInfo:
				Console_WriteAt(0, 2, msg);
				break;
			case mtStreamStart:
				Console_SetCursor(0, 3);
				// break;		// fall through
			case mtStream:
				Console_AdvanceToNextLineIfNotRoomFor(40);	// @TODO hard-coded - ick
				if (attrib) {
					Console_SetColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY);
				}
				Console_Write(msg);
				if (attrib) {
					Console_RestoreColor();
				}
				break;
			case mtXmtdMsg:
				Console_SetCursor(0, -1);
				EmitBuffer(">", (const uint8_t *)msg, attrib, true);
				break;
			case mtRcvdMsg:
				Console_SetCursor(0, -1);
				EmitBuffer("<", (const uint8_t *)msg, attrib, true);
				break;
			case mtExtendedResp:
				Console_SetCursor(0, -1);
				EmitBuffer("[", (const uint8_t *)msg, attrib, true);
				break;
			case mtStatusMsg:
				Console_SetCursor(0, -1);
				EmitBuffer("}", (const uint8_t *)msg, attrib, true);
				break;
			case mtOperationResp:
				// This is just the echo of the command
				// so there is nothing to report that wouldn't be a duplicate
				//Console_SetCursor(0, -1);
				//EmitBuffer("{", (const uint8_t *)msg, attrib, true);
				break;
			case mtParamUpdated:
				message = "x";
				break;
			case mtErrNotFound:
				sprintf_s(buf, MAXTEXTLEN, "AVRCommand(%02X,%02X,%02X) -- not found", (attrib >> 16), (attrib >> 8) & 0xFF, attrib & 0xFF);
				Console_WriteAt(0, -1, buf);
				break;
			case mtErrRxLength:
				break;
			case mtErrMalloc:
				break;
			case mtFailed:
				break;
			default:
				break;
		}
	}
	return true;
}

void EmitCommandLineHelp() {
	const char * by = "by Smartware Computing";
	printf("%s [Options] %80s\n", progname, by);
	printf("\n");
	printf("  This program can control a Yamaha AVR RX-V2400 receiver via the RS-232 port.\n");
	printf("  It may work for other similar models.\n");
	printf("\n");
	printf("  This software, and/or material is the property of Smartware Computing. All use,\n");
	printf("  disclosure, and/or reproduction not specifically authorized by Smartware\n");
	printf("  Computing is prohibited. This program has no warranty or declaration of fitness\n");
	printf("  for your use, and no claim that it will function as intended. While the author is\n");
	printf("  using this regularly with an RX-V2400 receiver, it was developed using the full\n");
	printf("  capability expressed in the Yamaha RS232C Standard document, even though not all\n");
	printf("  features have been tested and NOT ALL AVRs support all commands.\n");
	printf("\n");
	printf("  There is no claim that it cannot damage your AVR, and you are accepting the risk!\n");
	printf("\n");
	printf("  Reference Documents:\n");
	printf("    + Yamaha RX-V2400 RS232C Standard - with decoding of most parameters.\n");
	printf("    + Yamaha RX-V2400 RS232C Extended - raw hex streams are supported.\n");
	printf("\n");
	printf("  This program is copyright (c) 2025 by Smartware Computing, all rights reserved.\n");
	printf("\n");
	printf("  Author David Smart, Smartware Computing\n");

	printf("\n");
	printf("Options:\n");
	printf("\n");
	printf("    -SerialPort=X[,yyyy]     Set to Serial Port X to baud rate yyyy.\n");
	printf("                               Defaults baud is %d\n", avrBaud);
	printf("                               NOTE: At least for the RX-V2400, 9600,8,N,1 is required.\n");
	printf("                               NOTE: At least for the RX-V2400, the host must assert RTS.\n");
	printf("\n");
	printf("    -Command=S,F,O[,V]       Execute command and exit (based on the numeric values):\n");
	printf("                               S - Subsystem,\n");
	printf("                               F - Function,\n");
	printf("                               O - Operation,\n");
	printf("                               V - Value used by only certain commands. This value is\n");
	printf("                                   entered as a decimal value (like the S,F, and O).\n");
	printf("                               Use the -ExportInfo command to see the Command List.\n");
	printf("                               NOTE: Program will auto-exit after performing the command,\n");
	printf("                                     or after about 5 seconds without obvious success.\n");
	printf("\n");
	printf("    -Extended=xxxxxxxx...    Send Extended Command defined by ASCII hex sequence of DT0...DTx.\n");
	printf("                             The length and checksum will be computed in the overall message.\n");
	printf("                               [DC4][SW][KIND][L0][L1][DT0][DT1]...[DTx][SUM0][SUM1][ETX]\n");
	printf("\n");
	printf("    -Verbose                 Shows progress during -Command operation.\n");
	printf("                               NOTE: %s is always verbose in interactive mode.\n", progname);
	printf("\n");
	printf("    -ExportInfo[=S[,F[,O]]]  Export AVR Control Parameters and exit, optionally \n");
	printf("                             filtered to a subsystem, function, and operation.\n");
	printf("                               NOTE: The unfiltered list is quite long.\n");
	printf("\n");
	printf("Handy Commands:              Here's a few commands, made 'simpler' to access.\n");
	printf("\n");
	printf("    -Power=On|Off            Control Main Power - On or Off\n");
	printf("    -Zone1Volume=+xx.y       Set the zone 1 volume db level in the range -80.0 to +16.5.\n");
	printf("    -Zone2Volume=+xx.y       Set the zone 2 volume db level in the range -80.0 to +16.5.\n");
	printf("    -Zone3Volume=+xx.y       Set the zone 2 volume db level in the range -80.0 to +16.5.\n");
	printf("                             CAUTION: Take care that you don't blow your speakers/ears!\n");
	printf("    -Mute=On|Off             Mute on or Mute off\n");
	printf("\n");
	printf("ini File:\n");
	printf("\n");
	printf("    %s.ini, if it exists in the same folder as the program, will be read before any command\n", progname);
	printf("    line parameters are processed. Supported items: \n");
	printf("      + Either form of the SerialPort command.\n");
	printf("      + Anything else is silently ignored.\n");
	printf("\n");
	printf("Examples:\n");
	printf("\n");
	printf("    > avr -ExportInfo=1,0                      (See the options for Main Power)\n");
	printf("    > avr -SerialPort=2 -Command=1,0,0         (Turn Main Power On)\n");
	printf("\n");
	printf("Exit Codes:\n");
	printf("\n");
	printf("    0 = Normal exit\n");
	printf("    1 = Illegal Option or unrecognized command\n");
	printf("    2 = Program Internal Error - Data table failed santity check.\n");
	printf("    3 = External Quit (e.g. Windows command to close).\n");
	printf("    4 = Something very abnormal happened to cause exit.\n");
	printf("\n");
	if (avrOnPort == COM_NO_PORT) {
		EnumerateComPorts();
	}
	exit(EXIT_OK);
}

DWORD AppTime() {
	static bool init = false;
	static DWORD startTime = 0;
	if (!init) {
		init = true;
		startTime = timeGetTime();
	}
	return timeGetTime() - startTime;
}

void ReadIniFile() {
	char buf[MAXTEXTLEN] = "";
	FILE *fp = fopen(ininame, "r");
	int param1, param2;

	if (fp) {
		while (!feof(fp)) {
			fgets(buf, sizeof(buf) - 1, fp);
			if (1 == sscanf_s(buf, "-SerialPort=%d", &param1)) {
				avrOnPort = param1;
			} else if (2 == sscanf_s(buf, "-SerialPort=%d,%d", &param1, &param2)) {
				avrOnPort = param1;
				avrBaud = param2;
			}
		}
	}
}

/******************************************************/
/*                 m a i n ( )                        */
/******************************************************/

//   0                                                              Width
//	+---------------------------------------------------------------+
//	| Program banner information                                    | 0
//	| State machine status information starts on line 2             |
//	| Overall information is on line 3 and onward                   |
//	|                                                               |
//	|                                                               |
//	|                                                               |
//  + Down near the bottom is the scroll region                     + 59
//	|                                                               |
//	|                                                               |
//	|                                                               | 79
//	+---------------------------------------------------------------+ 80

int __cdecl main(int argc, char *argv[]) {
	short consoleWidth = 122;
	short consoleHeight = 80;
	short consoleScrollHeight = 30;
	short consoleLeft = 50;
	short consoleTop = 15;
	int CommandToExecute[4] = { -1, -1, -1, -1 };
	float db;
	int parseCount = 0;
	char *pExtendedMessage = NULL;

	// MessageHandlerSanityCheck();	// If the table is bad, we exit here
	GetProgName(argv[0]);
	UserCommandsSanityCheck();	// If the table is bad, we exit here
	
	avr = new AVRInterface(InformationUpdate, SerialSend);

	ReadIniFile();

	for (int i = 1; i < argc; i++) {
		int param1, param2;
		if (1 == sscanf_s(argv[i], "-SerialPort=%d", &param1)) {
			avrOnPort = param1;
		} else if (2 == sscanf_s(argv[i], "-SerialPort=%d,%d", &param1, &param2)) {
			avrOnPort = param1;
			avrBaud = param2;
		} else if (3 == sscanf_s(argv[i], "-Command=%d,%d,%d", &CommandToExecute[0], &CommandToExecute[1], &CommandToExecute[2])) {
			// After the port is open, execute this and exit
		} else if (4 == sscanf_s(argv[i], "-Command=%d,%d,%d,%d", &CommandToExecute[0], &CommandToExecute[1], &CommandToExecute[2], &CommandToExecute[3])) {
			// After the port is open, execute this and exit
		} else if (0 == strncmp(argv[i], "-Extended=", 10)) {
			pExtendedMessage = argv[i] + 10;
		} else if (0 == strcmp(argv[i], "-Verbose")) {
			verboseMode = true;
		} else if (0 == strcmp(argv[i], "-ExportInfo")) {
			bool saveVerbose = verboseMode;
			verboseMode = true;
			avr->ExportInformation();
			verboseMode = saveVerbose;
			exit(EXIT_OK);
		} else if (1 <= (parseCount = sscanf_s(argv[i], "-ExportInfo=%d,%d,%d", &CommandToExecute[0], &CommandToExecute[1], &CommandToExecute[2]))) {
			//
			// // If they didn't provide all three, set the others to default (wildcard)
			//
			switch (parseCount) {
			case 1:
				CommandToExecute[1] = AVRFunction_T::fncFunctionCount;
				// break;	// fall thru on purpose
			case 2:
				CommandToExecute[2] = AVRArg_T::eARGCount;
				// break;	// fall thru on purpose
			default:
				// nothing to do
				break;
			}
			bool saveVerbose = verboseMode;
			verboseMode = true;
			avr->ExportInformation((AVRSubsystem_T)CommandToExecute[0],
				(AVRFunction_T)CommandToExecute[1],
				(AVRArg_T)CommandToExecute[2]);
			verboseMode = saveVerbose;
			exit(EXIT_OK);
		} else if (0 == strcmp(argv[i], "-Power=On")) {
			CommandToExecute[0] = subMain;
			CommandToExecute[1] = fncPower;
			CommandToExecute[2] = eOn;
		} else if (0 == strcmp(argv[i], "-Power=Off")) {
			CommandToExecute[0] = subMain;
			CommandToExecute[1] = fncPower;
			CommandToExecute[2] = eOff;
		} else if (0 == strcmp(argv[i], "-Mute=On")) {
			CommandToExecute[0] = subMain;
			CommandToExecute[1] = fncMute;
			CommandToExecute[2] = eOn;
		} else if (0 == strcmp(argv[i], "-Mute=Off")) {
			CommandToExecute[0] = subMain;
			CommandToExecute[1] = fncMute;
			CommandToExecute[2] = eOff;
		} else if (1 == sscanf_s(argv[i], "-Zone1Volume=%4f", &db)) {
			if (db >= -80.0f && db <= +16.5f) {
				CommandToExecute[0] = sysCommand;
				CommandToExecute[1] = fncSetValue;
				CommandToExecute[2] = eMasterVol;
				CommandToExecute[3] = avr->VolumeDBtoAPIValue(db);
			} else {
				printf("***** Volume %+4.1f not in the range '-80.0 <= value <= 16.5' *****\n", db);
				exit(EXIT_IllegalOption);
			}
		} else if (1 == sscanf_s(argv[i], "-Zone2Volume=%4f", &db)) {
			if (db >= -80.0f && db <= +16.5f) {
				CommandToExecute[0] = sysCommand;
				CommandToExecute[1] = fncSetValue;
				CommandToExecute[2] = eZone2Vol;
				CommandToExecute[3] = avr->VolumeDBtoAPIValue(db);
			} else {
				printf("***** Volume %+4.1f not in the range '-80.0 <= value <= 16.5' *****\n", db);
				exit(EXIT_IllegalOption);
			}
		} else if (1 == sscanf_s(argv[i], "-Zone3Volume=%4f", &db)) {
			if (db >= -80.0f && db <= +16.5f) {
				CommandToExecute[0] = sysCommand;
				CommandToExecute[1] = fncSetValue;
				CommandToExecute[2] = eZone3Vol;
				CommandToExecute[3] = avr->VolumeDBtoAPIValue(db);
			} else {
				printf("***** Volume %+4.1f not in the range '-80.0 <= value <= 16.5' *****\n", db);
				exit(EXIT_IllegalOption);
			}
		} else {
			printf("***** Unrecognized command '%s' *****\n", argv[i]);
			EmitCommandLineHelp();
			exit(EXIT_IllegalOption);
		}
	}
	if (argc == 0 || avrOnPort == COM_NO_PORT) {
		EmitCommandLineHelp();
		exit(EXIT_OK);
	}
	
	if (AttachToSerialPort()) {
		if (pExtendedMessage || (CommandToExecute[0] != -1 && CommandToExecute[1] != -1 && CommandToExecute[2] != -1)) {
			DWORD refTime = AppTime();
			DWORD nowTime = AppTime();
			DWORD elapsedTime = nowTime - refTime;
			AVRInterface::AVRState_T state = AVRInterface::stMaxStates;
			do {
				state = avr->Tick(nowTime);
				Sleep(10);	// @TODO how long to wait should not be based on lucky timing
				SerialQueue_T anyRcvdMsg = ProcessSerialReceive();
				if (anyRcvdMsg.len) {
					avr->HandleMessage(anyRcvdMsg.message, anyRcvdMsg.len);
				}
				nowTime = AppTime();
				elapsedTime = nowTime - refTime;
			} while ((elapsedTime < 5000) && (state != AVRInterface::stReady));

			if (elapsedTime < 5000) {
				if (pExtendedMessage) {
					SendExtendedMessage(pExtendedMessage);
				} else {
					avr->AVRCommand((AVRSubsystem_T)CommandToExecute[0],
						(AVRFunction_T)CommandToExecute[1],
						(AVRArg_T)CommandToExecute[2],
						(uint8_t)CommandToExecute[3]);
				}
				refTime = nowTime = AppTime();
				do {
					Sleep(10);	// @TODO how long to wait should not be based on lucky timing
					nowTime = AppTime();
					SerialQueue_T anyRcvdMsg = ProcessSerialReceive();
					if (anyRcvdMsg.len) {
						avr->HandleMessage(anyRcvdMsg.message, anyRcvdMsg.len);
					}
					state = avr->Tick(nowTime);
				} while (((nowTime - refTime) < 5000) && (state != AVRInterface::stReady));
			}
			DetachSerialPort();
			exit(EXIT_OK);
		}

		verboseMode = true;		// Always verbose when entering interactive mode
		Console_Init(consoleLeft, consoleTop, consoleWidth, consoleHeight, consoleScrollHeight);
		Console_Cls();
		if (!SetConsoleCtrlHandler(ControlIntercept, TRUE)) {
			printf("WARN: Cannot install Console Control Handler\n");
		}

		printf("AVR - a command shell utility for a Yamaha RX-V2400 AVR by D.Smart        ");
		printf("[COM%i at %i baud]", avrOnPort, avrBaud);
		Console_WriteAt(0, consoleHeight - consoleScrollHeight -1, "----------------------------------------");

		do {
			avr->Tick(AppTime());
			ProcessKeyboard();
			SerialQueue_T anyRcvdMsg = ProcessSerialReceive();
			if (anyRcvdMsg.len) {
				uint8_t offsetChanged = avr->HandleMessage(anyRcvdMsg.message, anyRcvdMsg.len);
				
				if (offsetChanged) {
					bool priorState = Console_SetCursorVisibility(false);
					avr->ReportAllStatus(offsetChanged);
					Console_SetCursorVisibility(priorState);
				}
			}
			Sleep(10);
			ProcessWindowsMessage();
		} while (!UserExitRequested && !SystemExitRequested);
		DetachSerialPort();
	} else {
		printf("Failed to attach to Serial Port on COM%i\n", avrOnPort);
	}
	if (UserExitRequested) {
		exit(EXIT_OK);
	} else if (SystemExitRequested) {
		exit(EXIT_ExternalQuit);
	} else {
		exit(EXIT_AbnormalError);
	}
}

void SetData(uint8_t *targ, const uint8_t *string, size_t len = 0) {
	uint8_t *p = (uint8_t *)string;
	if (len == 0) len = strlen((const char *)string);
	memcpy(targ, p, len);
}

// Dec2Hex
//
// Convert a number to a hex-ascii string
// 
const uint8_t *Dec2Hex(uint32_t val, uint16_t numDigits) {
	static uint8_t buf[16];
	sprintf_s((char *)buf, 16, "%0*X", numDigits, val);
	return buf;
}

// Hex2Dec
//
//	All responses are pretty much Hex-ASCII, so
//	we sometimes want to convert it to decimal
//	This takes a buffer and converts the specified
//	number of characters.
//
unsigned long Hex2Dec(uint8_t *p, int dig) {
	unsigned long x = 0;
	while (dig--) {
		if (*p >= '0' && *p <= '9')
			x = x * 16 + *p - '0';
		else if (*p >= 'a' && *p <= 'f')
			x = x * 16 + 0x0a + *p - 'a';
		else if (*p >= 'A' && *p <= 'F')
			x = x * 16 + 0x0a + *p - 'A';
		p++;
	}
	return x;
}


bool CheckTheChecksum(uint8_t *szBuffer, uint32_t num) {
	uint8_t sum = 0;
	for (uint16_t i = 1; i < num - 3; i++) {
		sum += szBuffer[i];
	}
	uint8_t cksum = (uint8_t)Hex2Dec(&szBuffer[num - 3], 2);
	//printf("CheckSum: %02X v. %c%c\n", sum, szBuffer[num - 3], szBuffer[num - 2]);
	return (sum == cksum);
}



// ProcessSerialReceive
// 
// All character sequences end with \x03 (this is the equiv of a <cr>)
// 
//
SerialQueue_T ProcessSerialReceive() {
	uint8_t partialRx[MAXTEXTLEN] = { 0 };
	
	static uint8_t bufInUse = 0;
	static uint8_t messageBuf[2][MAXTEXTLEN] = { 0 };
	static uint8_t *p = messageBuf[bufInUse];	// used to fill the partialRx as data comes in
	SerialQueue_T retInfo = { NULL, 0 };

	#include <stdio.h>
	uint32_t num = rs232Port.Read(partialRx, MAXTEXTLEN);
	if (num) {
		for (uint32_t i = 0; i < num; i++) {
			*p = partialRx[i];
			if (*p++ == '\x03') {	// End of message
				*p = '\0';			// null terminate it
				// It is ready to return now
				retInfo.message = messageBuf[bufInUse];
				retInfo.len = (uint16_t)(p - messageBuf[bufInUse]);
				// Prepare the alternate buffer for the next message
				bufInUse = (++bufInUse & 1);
				p = messageBuf[bufInUse];	// Reset the buffer for the next message, which might be in partialRx
				*p = '\0';
			} else {
				*p = '\0';	// keep it null terminated
			}
		}
		if (messageBuf[0]) {
			//EmitBuffer("~", (const uint8_t *)messageBuf, 0, true);	// Show them the partial receipt if anything is there
		}
	}
	return retInfo;
}



void EnumerateComPorts() {
	printf("Serial Port Scan:\n");
	int foundPorts = 0;
	for (int pNum = COM_MIN_PORT; pNum < COM_MAX_PORT; pNum++) {
		char cBuf[20];	// generously sized

		sprintf_s(cBuf, sizeof(cBuf), "\\\\.\\COM%d", pNum);
		bool portFound = false;
		HANDLE port = CreateFile(cBuf, GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr);
		if (port == INVALID_HANDLE_VALUE) {
			uint32_t dwError = GetLastError();
			if ((dwError == ERROR_ACCESS_DENIED) || (dwError == ERROR_GEN_FAILURE)
				|| (dwError == ERROR_SHARING_VIOLATION) || (dwError == ERROR_SEM_TIMEOUT)) {
				foundPorts++;
				portFound = true;
			}
		} else {
			foundPorts++;
			portFound = true;
			CloseHandle(port);
		}
		if (portFound) {
			printf("    Serial Port %d found.\n", pNum);
		}
	}
	if (foundPorts == 0) {
		printf("    No Serial Ports found, perhaps you need to plug in an adapter?\n");
	}
}


int DetachSerialPort() {
	if (rs232Port.IsOpen()) {
		rs232Port.Close();
	}
	return true;
}

int AttachToSerialPort() {
	bool success = false;
	if (avrOnPort >= COM_MIN_PORT && avrOnPort <= COM_MAX_PORT) {
		char buf[20];	// generously sized.

		sprintf_s(buf, sizeof(buf), "\\\\.\\COM%d", avrOnPort);
		uint32_t Access = GENERIC_WRITE | GENERIC_READ;
		if (rs232Port.Open(buf, avrBaud, 8, NOPARITY, ONESTOPBIT, Access)) {
			success = true;
			rs232Port.Set_RTS_State(false);
			Sleep(50);
			rs232Port.Set_RTS_State(true);
			Sleep(50);
		}
	}
	return success;
}

