// VPcmdWinConsole.cpp : Defines the entry point for the console application.
// VSS $Header: /Viper/VPCmdIF/VPcmdConsole/VPcmdWinConsole.cpp 20    6/01/20 3:46p Suzanne $
//

///
/**
@details 
# General instructions for using VPcmdLib to discover viper SEUs, configure, and collect P&O

1. Initialize the VPcmdIF and create a @ref vpcmd_context.

    The @ref vpcmd_context is the handle by which you will manage your connection to the associated device(s).

    + Call vpcmdif_init( vpcmd_context &).  
    + The context will be returned in the vpcmd_context & argument.
    + This is demonstrated in the Connect() function below.
    + The @ref vpcmd_context will now be used in almost every call into the VPcmdIF.


2. Use the @ref vpcmd_context to get a device handle (@ref vpdev_hnd)

    This operation creates a connection to the Viper SEU.
    + Call either 
        + vpctx_connectusb( vpcmd_context, vpdev_hnd &, uint16_t ) or 
        + vpctx_connectser(vpcmd_context, vpdev_hnd &, int32_t ) .
    + This is demonstrated in function Connect() below.
    + The @ref vpdev_hnd will now be used to specify this device.  It is possible to create >1 connection and have >1 device handle.


3. Some notes about VPcmdIF function naming conventions:
    +    VPcmdIF API-centric function names begin with **vpcmdif_**
    +    Context-centric function names begine with **vpctx_**
    +    Device-centric function names begin with **vpdev_**
    +    Configuration command function names pertaining to the device begin with **vpcmd_dev_**
    +    Configuration command function names pertaining to the sensor begin with **vpcmd_sns_**
    +    Sensor-ids in **vpcmd_sns_** function arguments may be wildcarded by using -1.
    +    All sensor- and device-id numbers are zero-based in the VPcmdIF and tracker interface constructs.


4. Trace Output

    By default, the VPcmdIF trace output is disabled.  In this sample app, it is enabled and set to level 3, "normal I/O trace".
    +    Use the Options menu **T** selection to try different levels and see more of the I/O between the host and devices.
    +    To use trace functions in your own code, include VPTrace.h.

5. P&O

    Tracker Position and Orientation ("P&O") data can be captured as single frames or in a continuous stream.
    +    Single Frame: 
        +    Call vpcmd_dev_singlepno().
        +    This is demonstrated in GetSingle() below.
        +    Single frames are returned in the PAYLOAD of the vpcmd_dev_singlepno() command response.
        +    In this payload, the referenced pno frame is of type @ref SEUPNO; it does not include the leading PREAMBLE and SIZE fields.
    +    Continuous:
        +    Call vpcmd_dev_contpno() to start continuous data output in the Viper.
        +    Demonstrated in StartCont() below.

             To collect Continuous streamed frames: 

             There are several VPcmdIF methods for collecting streamed P&O.  The choice depends on how the caller is using the data.

                a. If it is required that ALL data is collected, for post processing and analysis, then use the **vpcmd_dev_fifo...()** functions.  
                    + **FIFO** means "first in, first out". So the caller will collect the next unread p&o frame from the collection buffer.

                b. To capture only the most current position of the tracked object for real time simulations or VR, use the **vpcmd_dev_last...()** functions.
                    - This is demonstrated in ContLoop() below.

                c. Finally, there is one other variation on the Pno functions: '**Raw**' or '**PNO**'.
                    + **Raw Frames** include the entire Viper Frame PREAMBLE leader, size, and CRC. 
                    + **PNO frames** don't include the leader and size.  
                    + In either of these methods, the referenced data is in the same collection buffer.
                    + The reason for the two types is so that the same code can be used to parse and display single frames as is used for continuous frames.  
                        (Recall that single frames are picked out of the Single P&O command's payload and do not include the preamble and size.)
                    + This program uses the **PNO** frame capture rather than the Raw Frame capture for this reason.
                    + This is demonstrated in ContLoop() below.

6. A note about frames.  
    +    All Frames of data (CMD or PNO) received from devices are referenced by an instance of vpFrameInfo struct. 
    +    vpFrameInfo contains a pointer to the raw frame buffer and the size of the buffer. 

7. Command structures.  
    +    The ViperInterface.h header declares the raw command structures as consumed by the Viper device.
    +    The VPif.h file includes C++ classes for these same structures.
    +    The C++ classes provide a common programming interface for initializing/copying/filling/preparing/printing/exporting contents of the command struct without writing any additional code.

        NOTE: VPcmdIF Report functions, or any other functions that you may find that use STD C++ 11 containers will require extra care when used by a calling app.  Especially for reports, make sure that the std::string & passed into the report function already has reserved space to hold the generate string without allocating additional space.

         + If the Report function causes a realloc in these objects there will be heap exceptions.  
         + Call std::string::reserve() first.  
         + Likewise, if you are passing in STD C++ objects that are not compatible with the STD C++ build in VPcmdIF, you will likely have problems.  This is why the main VPcmdIF interface does not include STD C++ objects.  
         + You can get around most of this by pulling in the VPif.h and VPif.cpp files into your own local project and building them yourself.


# Building with VPcmdIF
## Build Notes
+    All builds of the VPcmdIF binaries are UNICODE.

+    The VPcmdIF is available as a DLL.  A future build will also produce a static library.
+    Use of static library is not recommended unless the target app is built with the same compiler and linker as VPcmdIF.
+    Input names and file locations *will change* when the VPcmdIF is formally released.

## Header Files
At a minimum your code will need to include the following header files:
- VPcmdIF.h
- VPtrace.h -- only if you want use trace output in your own code.

## Build settings
### Compiler:
    + Include Dirs:    <b>\$(Installdir)\\Inc</b>

### Linker:
    If using the VPcmdIF as a DLL:
    + Dirs:		**$(Installdir)\\\$(Platform)\\\$(Configuration)\\dll**
    + Inputs:		**VPcmdIF.lib**


**/
///@file VPcmdWinConsole.cpp
/// Windows Console Sample app for VPcmdIF SDK.

#include "stdafx.h"
#include "Pconapp.h"
#include <future>
#include <thread>

#include "VPcmdIF.h"
#include "VPtrace.h"
#include "VPserIO.h"
#include "VPlocalIF.h"

#define POLHEMUS_MFR_STR _T("  POLHEMUS")
#define POLHEMUS_VID 0x0f44

uint16_t g_vid = POLHEMUS_VID;
TCHAR * g_vid_name = POLHEMUS_MFR_STR;
extern int g_serport = 1;
#define SUCCESS(r) (r==0)
#define FAIL(r) (r != 0)

vpcmd_context g_ctx;
vpdev_hnd     g_hnd;
uint32_t   g_seuid = 0;
int        g_snsid = 0;
int        g_srcid = 0;
int		   g_TraceLevel = VPCMD_TRACE_3_NORMALIO;
int		   g_bTraceMode = true;
bool	   g_bFIFO = true;
bool	   g_bTimestamp = false;
std::chrono::steady_clock::time_point g_starttime;
uint64_t g_start = 0; 
uint32_t DisplayOneRawFrame(void*, CVPSeuPnoL &);
void	DoFrameTest0();
void	DoFrameTest1();
int		FrameTestLoop();
CFrameRateCfg g_cfrate;
bool g_bRunFT = true;

void DisplayRecordedBlock();
void CALLBACK AsyncPnoReady(void* pInfo, uint8_t* pdata, uint32_t nbytes);
std::vector<uint32_t> g_pno(1024);
uint32_t g_frame{}, g_lastframe{};
uint32_t g_tot_frames{}, g_lost_frames{};

#include "InputFcns.h"
#include "CommandTest.h"


int main()
{
	std::chrono::system_clock::time_point now;
	tcout << _T("uint64_t size = ") << sizeof(uint64_t) << endl;
	tcout << _T("LARGE_INTEGER size = ") << sizeof(LARGE_INTEGER) << endl;
	tcout << _T("std::chrono::system_clock::time_point size = ") << sizeof(now) << endl;

	eMenuChoice eChoice = CHOICE_NONE;

	SetConsoleTitle(_T("VPcmdConsole " __DATE__ " " __TIME__) );
	tcout << _T("VPcmdWinConsole built with VPcmdIF version ") << vpcmdif_version() << "  " << vpcmdif_date() << _T(" ") << vpcmdif_time() << endl;

	HANDLE hproc = ::GetCurrentProcess();
	//DWORD priclass0 = ::GetPriorityClass(hproc);
	//::SetPriorityClass(hproc, HIGH_PRIORITY_CLASS);
	//DWORD priclass1 = ::GetPriorityClass(hproc);
	//std::cout << "VPcmdWinConsole process priority class = " << priclass1 << endl;

	if (ConnectPrompt() == CHOICE_QUIT)
	{
		eChoice = CHOICE_QUIT;
	}
	else
	{
		ShowMainMenu();
		do
		{
			eChoice = MainPrompt();

			switch (eChoice)
			{
			case CHOICE_TEST_NEWCMD:
				DoCalDisableTest();
				break;

			case CHOICE_CONT:
				//Start Cont Mode
				if (!(StartCont()))
				{
				}
				else
				{
					//Collect/Display Tracker Data until ESC
					ContLoop();
					StopCont();
				}

				break;

			case CHOICE_SINGLE:
				GetSingle();
				break;

			case CHOICE_TRACEMODE:
				ToggleTraceMode();
				break;

			case CHOICE_TRKCMDS:
				{
					eMenuChoice eCmdChoice = CHOICE_NONE;
					do
					{
						eCmdChoice = CommandsPrompt();
						switch (eCmdChoice)
						{
						case CMD_CHOICE_DEVSEL:
							break;
						case CMD_CHOICE_WHOAMI:
						case CMD_CHOICE_STAMAP:
						case CMD_CHOICE_UNITS:
						case CMD_CHOICE_SEUID:
						case CHOICE_QUIT:
						default:
							break;
						}
					} while (eCmdChoice != CHOICE_QUIT);
					tcout << _T("\n\n");
					ShowMainMenu();
				}
				break;

			case CHOICE_OPTIONS:
				OptionsPrompt();
				break;

			case CHOICE_ENTER:
				ShowMainMenu();
				break;

			case CHOICE_RESTART:
				eChoice = ConnectPrompt();
				break;

			case CHOICE_DISCONNECT:
				Disconnect();
				eChoice = ConnectPrompt();
				if (eChoice != CHOICE_QUIT)
					ShowMainMenu();
				break;

			default:
				break;
			}

		} while (eChoice != CHOICE_QUIT);

		Disconnect();

	}
	//::SetPriorityClass(hproc, priclass0);
	//priclass1 = ::GetPriorityClass(hproc);
	//std::cout << "VPcmdWinConsole process priority class = " << priclass1 << endl;

	return 0;
}

//
// function definitions for adapting this program to alternate trackers
//
typedef int (*usbcnxfunc)(vpcmd_context, vpdev_hnd &, uint16_t);
struct _cnx_fxn_info
{
	usbcnxfunc func;
	const char * szfunc;
};
_cnx_fxn_info usbcnx_fxn_info[TRACKER_MAX] =
{
	vpctx_connectusb, "vpctx_connectusb",
};
typedef int(*singlefunc)(vpcmd_context, vpdev_hnd, vpFrameInfo &, ePnoMode);
struct _single_fxn_info
{
	singlefunc func;
	const char *szfunc;
};
_single_fxn_info singlepno_fxn_info[TRACKER_MAX] =
{
	vpcmd_dev_singlepno, "vpcmd_dev_singlepno",
};
//
//local definition of PNO frame that does not include the preamble and size fields.
//
typedef struct _MYPNO {
	SEUPNO seupno;
	SENFRAMEDATA sarr[SENSORS_PER_SEU];
}MYPNOTYPE;
int32_t		DisplayNativeFramePNO(MYPNOTYPE*, uint64_t * pts = 0);

//! [Initializing]
bool Connect()
{
	bool bRet = false;
	int  r = 0;
	std::string str;

	size_t ctxsize = sizeof(g_ctx);
	
	vpcmdif_trace(g_bTraceMode ? g_TraceLevel : 0);
	if (r = vpcmdif_init(g_ctx))
	{
		//error initializing vpcmdif lib
		DisplayError(r, "vpcmdif_init");
	}
	else if (r = usbcnx_fxn_info[g_TrackerType].func(g_ctx, g_hnd, g_pid))
	{
		DisplayError(r, usbcnx_fxn_info[g_TrackerType].szfunc);
	}
	else
	{
		bRet = true;
		DisplayError(r, "vpctx_connectusb");

		DisplaySEUs();
	}

	return bRet;
}
//! [Initializing]

bool ConnectSer()
{
	bool bRet = false;
	int  r = 0;
	std::string str;

	vpcmdif_trace(g_TraceLevel);
	if (r = vpcmdif_init(g_ctx))
	{
		//error initializing vpcmdif lib
		DisplayError(r, "vpcmdif_init");
	}
	else if (r = vpctx_connectser(g_ctx, g_hnd, g_serport))
	{
		DisplayError(r, "vpctx_connectser");
	}
	else
	{
		bRet = true;
		DisplayError(r, "vpctx_connectser");

		DisplaySEUs();
	}

	return bRet;
}

void DisplaySEUs()
{
	const char *szrpt = 0;
	int r = 0;
	if (r = vpctx_dev_report(g_ctx, szrpt))
	{
		DisplayError(r, "vpctx_report");
	}

	cout << endl << "SEUs Info:" << endl;
	std::cout << "ID   : Serial Num       : Name              " << endl;
	std::cout << "------------------------------------------------" << endl;
	if (szrpt)
	{
		cout << szrpt;
	}
	else
		cout << "None found." << endl;

}

void DisplayError(int32_t err, const char *szMsg)
{
	cout << szMsg << " : " << vpcmdif_errorStr(err);

	cout << endl;
}

void Disconnect()
{
	int r = vpcmdif_release(g_ctx);
	DisplayError(r, "vpcmdif_release");
};

void ListSerialPorts()
{
	tstring s;
	CVPserIO::PortListReport(s);
	tcout << endl;
	tcout << s.c_str();
	tcout << endl;
}

/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
void GetWhoAmI()
{
	int r = 0;

	cout << endl << "VPCmdIF version: " << vpcmdif_version() << endl;

	const char *szrpt = 0;

	if (r = vpcmd_dev_whoami_report(g_ctx, g_hnd, szrpt))
		DisplayError(r, "vpcmd_dev_whoami_report");

	else
		cout << szrpt << endl;

	//TODO: Make this handle multiple devices

}
void GetWhoAmISensor(int nsensor)
{
	int r = 0;

	const char *szrpt = 0;

	if (r = vpcmd_sns_whoami_report(g_ctx, g_hnd, nsensor, szrpt))
		DisplayError(r, "vpcmd_sns_whoami_report");

	else
		cout << szrpt << endl;

}
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
bool FlushPnoBuffer()
{
	int r = 0;
	r = vpcmd_dev_reset_framecount(g_ctx, g_hnd);
	DisplayError(r, "vpcmd_dev_reset_framecount");

	vpFrameInfo f;	///frame structs
	bool bdata = true;

	while (bdata)
	{
		if (r = g_bFIFO ? vpctx_dev_fifopnof(g_ctx, g_hnd, f) : vpctx_dev_lastpnof(g_ctx, g_hnd, f))
		{
			//if (r != E_CTXERR_NO_MORE_DATA)
			DisplayError(r, g_bFIFO ? "vpctx_dev_fifopnof() failed with error " : "vpctx_dev_lastpnof() failed with error ");
			bdata = false;
		}
		else
		{
			if (f.uiSize)
				bdata = true;
			else
				bdata = false;
			DisplayNativeFrame(&f);
		}
	}
	return r == 0;
}
bool StartCont()
{
	int r = 0;
	if (r = vpcmd_dev_contpno(g_ctx, g_hnd, CMD_ACTION_SET))
		DisplayError(r, "vpcmd_dev_contpno SET");

	return r == 0;
}
bool StopCont()
{
	int r = 0;
	if (r = vpcmd_dev_contpno(g_ctx, g_hnd, CMD_ACTION_RESET))
		DisplayError(r, "vpcmd_dev_contpno RESET");

	return r == 0;
}
bool ContLoop(int nGet/* = -1*/)
{
	if (nGet < 0)
	{
		std::cout << "\nPress any key to start continuous display\r\n";
		std::cout << "\nPress ESC to stop.\r\n";
		std::cout << "\nReady? ";
		_getche();
		std::cout << std::endl;
	}

	bool bRet = true;
	bool bExit = false;
	int r;
	int nCaptured = 0;
	int nReads = 0;

	do {

		if ((nGet > 0) && (nReads > 500 * nGet))
		{
			bRet = false; // 
			break;
		}

		vpFrameInfo f;	///frame structs

		nReads++;
		if (r = g_bFIFO ? vpctx_dev_fifopnof(g_ctx, g_hnd, f) : vpctx_dev_lastpnof(g_ctx, g_hnd, f))
		{
			if (r != E_CTXERR_NO_MORE_DATA)
				DisplayError(r, g_bFIFO ? "vpctx_dev_fifopnof() failed with error " : "vpctx_dev_lastpnof() failed with error ");

		}
		else
		{
			nCaptured++;

			DisplayNativeFrame(&f);

			//MYPNOTYPE	* pdevFrame;

			//pdevFrame = (MYPNOTYPE*)(f.pF);
			//std::cout << "cont hfc: " << f.uiFCountRx << ", dev fc: " << pdevFrame->seupno.frame << std::endl;

		}

		if (nGet >= 0)
			bExit = (nCaptured >= nGet);

		if (_kbhit() && (_getch() == ESC))
		{
			bExit = true;
		}

	} while (!bExit);

	trTracefHP("%s : %d reads produced %d captured of %d requested\r\n", __FUNCTION__, nReads, nCaptured, nGet);


	return bRet;
}

void DoFrameTest1()
{
	//HANDLE hproc = ::GetCurrentProcess();
	//DWORD priclass0 = ::GetPriorityClass(hproc);
	//if (!(::SetPriorityClass(hproc, REALTIME_PRIORITY_CLASS)))
	//{
	//	DWORD err = GetLastError();
	//	std::cout << "DoFrameTest1 error setting process priority class to REALTIME : " << err << endl;
	//}
	//DWORD priclass1 = ::GetPriorityClass(hproc);
	//std::cout << "DoFrameTest1 process priority class = " << priclass1 << endl;

	vpdev_SetPnoCallback(g_ctx, g_hnd, AsyncPnoReady, nullptr);
	//FlushPnoBuffer();
	//g_cfrate.InitDefault();
	//vpcmd_dev_framerate(g_ctx, g_hnd, CMD_ACTION_GET, g_cfrate);
	if (!(StartCont()))
	{
	}
	else
	{
		g_bRunFT = true;
		//int quit;
		char szquit[10] = { 0 };
		std::cin.clear();

		//cout << "launching FrameTestLoop. &g_bRunFT = " << hex << &g_bRunFT << " , g_bRunFT = " << (g_bRunFT ? " true " : " false ") << endl;
		////std::future<int> fut = std::async(FrameTestLoop, std::ref<bool>(run));
		//std::future<int> fut = std::async(std::launch::async, FrameTestLoop);

		//std::cout << "Synchronous 1ms\n";
		std::cout << "any key + Enter to quit\n";
		std::cin.getline(szquit, 10);

		//std::cin >> quit;   // enter any key to quit
		//std::cin.ignore();
		cout << "cin getline szquit: ";
		for (int i = 0; i < 10; i++)
			cout << " " << hex << (uint8_t)(szquit[i]);// << endl;

		cout << endl;
		g_bRunFT = false;

		
		std::cout << "Read " << dec << g_tot_frames << " total frames with " << g_lost_frames << "  lost frames\n";
		//std::cout << "Total frames missed " << dec << g_lost_frames << '\n';
	}
	vpdev_SetPnoCallback(g_ctx, g_hnd, nullptr, nullptr);
	StopCont();
	//::SetPriorityClass(hproc, priclass0);
	//priclass1 = ::GetPriorityClass(hproc);
	//std::cout << "DoFrameTest1 process priority class = " << priclass1 << endl;
}
void CALLBACK AsyncPnoReady(void* pInfo, uint8_t* pdata, uint32_t nbytes)
{
	if (!g_bRunFT)
		return;

	//HANDLE hth = ::GetCurrentThread();
	////::SetThreadPriority(hth, THREAD_PRIORITY_TIME_CRITICAL);
	//int thp = ::GetThreadPriority(hth);
	//if (thp == THREAD_PRIORITY_ERROR_RETURN)
	//{
	//	DWORD err = GetLastError();
	//}
	//else
	//	std::cout << "AsyncPnoReady thread priority " << thp << std::endl;

	if ((!g_bRunFT) && (nbytes != 0))
	{
		return;

	}
	else if ((nbytes == 0) || !g_bRunFT)
		return;

	uint32_t * ppno = (uint32_t *)pdata;
	if (g_lastframe == 0) // first time
	{
		//std::cout << "Read " << g_tot_frames << " total frames with " << g_lost_frames << "  lost frames\n";
		g_lastframe = ppno[3];
		return;
	}
	uint32_t frame{};
	frame = ppno[3];
	uint32_t seuid{};
	seuid = ppno[2];
	uint32_t missed_frames = frame - g_lastframe - 1;
	if ((missed_frames != 0) && (seuid == 0))
	{
		int i = 0;//wrong condition
		std::cout << " 0missed_frames = " << dec << missed_frames << " seuid = " << seuid << endl;
	}
	else if ((seuid != 0) && (missed_frames == 0))
	{
		int i = 0; //also wrong
		std::cout << " 1missed_frames = " << dec << missed_frames << " seuid = " << seuid << endl;
	}
	g_lost_frames += missed_frames;
	++g_tot_frames;
	g_lastframe = frame;
	if (g_tot_frames % 2400 == 0)
		std::cout << "Read " << g_tot_frames << " total frames with " << g_lost_frames << "  lost frames\n";


}

int GetOnePnoRawF(vpFrameInfo &f)
{
	int r = 0;
	if (r = vpctx_dev_fifopnofraw(g_ctx, g_hnd, f))
	{
		if (r != E_CTXERR_NO_MORE_DATA)
			DisplayError(r, "vpctx_dev_fifopnof() failed with error ");
	}
	return r;
}
int GetOnePnoRawL(vpFrameInfo &f)
{
	int r = 0;
	if (r = vpctx_dev_lastpnofraw(g_ctx, g_hnd, f))
	{
		if (r != E_CTXERR_NO_MORE_DATA)
			DisplayError(r, "vpctx_dev_lastpnofraw() failed with error ");
	}
	return r;
}

void DoFrameTest0()
{
	FlushPnoBuffer();
	g_cfrate.InitDefault();
	vpcmd_dev_framerate(g_ctx, g_hnd, CMD_ACTION_GET, g_cfrate);
	if (!(StartCont()))
	{
	}
	else
	{
		bool run;
		run = true;
		g_bRunFT = true;
		//int quit;
		char szquit[10] = { 0 };
		std::cin.clear();

		std::future<int> fut = std::async(std::launch::async, FrameTestLoop);

		std::cout << "any key + Enter to quit\n";
		std::cin.getline(szquit, 10);

		g_bRunFT = false;
		int flost = fut.get();

		std::cout << "Total frames missed " << dec << flost << '\n';
	}
}


int FrameTestLoop( )
{
	//Elevate thread if not using Fifo (GetOnePnoF) .. otherwise frames might be missed.
	HANDLE hth = ::GetCurrentThread();
	::SetThreadPriority(hth, THREAD_PRIORITY_TIME_CRITICAL);

	uint32_t er = g_cfrate.EffectiveRate();
	int32_t ms = g_cfrate.FrequencyMS();

	bool bRet = true;
	bool bExit = false;
	int r;
	int nCaptured = 0;
	int nReads = 0;
	int lastframe{ 0 }, frame{ 0 }, missed_frames{ 0 }, lost_frames{ 0 }, tot_frames{ 0 };

	CFrameInfo f;	///frame structs
	f.Init();

	//cout << "poll freq " << ms << "ms" << endl;
	std::this_thread::sleep_for(chrono::milliseconds(ms));
	//get first frame
	while FAIL(GetOnePnoRawL(f));
	{
		lastframe = f.devfc();
		cout << "First frame num is " << dec << lastframe << endl;
	}

	while (g_bRunFT) {
		//auto start = std::chrono::high_resolution_clock::now();
		r = GetOnePnoRawL(f);
		if (r == 0)
		{
			frame = f.devfc();
			if (frame != lastframe)
			{
				missed_frames = frame - lastframe - 1;
				lost_frames += missed_frames;
				++tot_frames;

				lastframe = frame;

				if (tot_frames % 2400 == 0)
					std::cout << endl << "Read " << dec << tot_frames << " total frames with " << lost_frames << "  lost frames\n";
			}
		}
		//while (std::chrono::high_resolution_clock::now() - start < 4ms);
	}
	StopCont();
	return lost_frames;
}

#define MAX_VP_PNO_FRAME_SIZE (sizeof(SEUPNO_HDR) + (SENSORS_PER_SEU * sizeof(SENFRAMEDATA)) + sizeof(uint32_t)) //24+(16*32)+4 540 0x21C 
//24 + 16*32 + 4 = 540 =16 sensors
#define MIN_VP_PNO_FRAME_SIZE (sizeof(SEUPNO_HDR) + (0 *               sizeof(SENFRAMEDATA)) + sizeof(uint32_t)) //24+4 = 28 0x1C
//24 + 4 = 28 = 0 sensors
#define DUR_SEC 10 //10 seconds
#define MOTION_BUFFER_SIZE MAX_VP_PNO_FRAME_SIZE * 240 * DUR_SEC 
//540 * 240 * 10 = 1296000 bytes

BYTE	g_pMotionBuf[MOTION_BUFFER_SIZE];
uint32_t m_nRecordedByteCount=0;
uint32_t m_nRecordBufferSize = MOTION_BUFFER_SIZE;
uint32_t m_nRecordBufferedBytes = 0;
INT_PTR	 m_nRecordedFrameCount = 0;

bool ContBlockLoop(int nGet/* = -1*/)
{
	if (nGet > 0)
	{
		std::cout << "\nReading " << nGet << " frames as available from tracker.\r\n";
		std::cout << "\nPress ESC to abort.\r\n";
		//std::cout << "\nReady? ";
		//_getche();
		//std::cout << std::endl;
	}

	bool bRet = true;
	bool bExit = false;
	int r;
	int nCaptured = 0;
	int nReads = 0;

	m_nRecordedByteCount = 0;
	m_nRecordBufferSize = MOTION_BUFFER_SIZE ;
	m_nRecordBufferedBytes = 0;
	m_nRecordedFrameCount = 0;

	do {

		if ((nGet > 0) && (nReads > 500 * nGet))
		{
			bRet = false; // 
			break;
		}

		//vpFrameInfo f;	///frame structs
		uint32_t fc_avail = 0;
		uint32_t bytes_avail = 0;

		nReads++;
		if (r = vpctx_dev_fifo_unread_count(g_ctx, g_hnd, fc_avail, bytes_avail))
		{
			DisplayError(r,"vpctx_dev_fifo_unread_count() failed with error ");

		}
		else if (fc_avail)
		{ 
			nReads++;

			uint32_t fc_read = fc_avail;
			uint8_t * pdest = &g_pMotionBuf[m_nRecordBufferedBytes];
			uint32_t uiSizeAvail = m_nRecordBufferSize - m_nRecordBufferedBytes;

			if (r = vpctx_dev_fifo_readrawbuf(g_ctx, g_hnd, fc_read, pdest, uiSizeAvail))//  vpctx_dev_lastpnof(g_ctx, g_hnd, f))
			{
				if (r != E_CTXERR_NO_MORE_DATA)
					DisplayError(r, "vpctx_dev_lastpnof() failed with error ");
			}
			else
			{
				m_nRecordBufferedBytes += uiSizeAvail;
				m_nRecordedFrameCount += fc_read;
				m_nRecordedByteCount += uiSizeAvail;

				//std::cout << fc_read << " new frames read; " << m_nRecordedFrameCount << " total. " << std::endl;
			}

		}
		else
		{
			//std::cout << fc_avail << " new frames available." << std::endl;

		}
		bExit = (m_nRecordedFrameCount >= nGet);

		if (_kbhit() && (_getch() == ESC))
		{
			bExit = true;
		}


	} while (!bExit);

	trTracefHP("%s : %d reads produced %d captured of %d requested\r\n", __FUNCTION__, nReads, m_nRecordedFrameCount, nGet);

	return bRet;
}
void DisplayRecordedBlock()
{
	if (m_nRecordedByteCount == 0)
	{
		std::cout << "\nNo recorded data\r\n";
		return;
	}
	std::cout << std::endl << m_nRecordedFrameCount << " frames, " << m_nRecordedByteCount << " bytes recorded in blocks.";
	std::cout << "\nPress any key to start display of recorded data\r\n";
	std::cout << "\nPress ESC to cancel.\r\n";
	std::cout << "\nReady? ";
	if (_getche() != ESC)
	{
		std::cout << std::endl;
		CVPSeuPnoL pno;

		bool bFirst = true;
		uint32_t prev_fc = 0;
		uint32_t drops = 0;
		uint32_t curr_fc = 0;
		bool bExit = false;
		uint32_t nPlayedBufferedBytes = 0;
		int32_t nUnplayedBytes = (uint32_t)m_nRecordBufferedBytes;
		do {
			vpFrameInfo f;	///frame structs
			f.pF = &g_pMotionBuf[nPlayedBufferedBytes];
			f.uiSize = nUnplayedBytes;

			uint32_t nFrameBytes = DisplayOneRawFrame(&f, pno);
			std::cout << flush;

			if (nFrameBytes)
				nUnplayedBytes -= nFrameBytes;
			else
				nUnplayedBytes = 0;

			nPlayedBufferedBytes += nFrameBytes;

			curr_fc = pno.seupno.frame;
			if (!bFirst)
			{
				uint32_t delta_fc = (curr_fc - prev_fc);
				uint32_t curr_drops = delta_fc - 1;
				drops += (curr_drops);
				//s = $"\t curr_fc: {curr_fc},  prev_fc: {prev_fc}, delta_fc: {delta_fc}, curr_drops: {curr_drops}, total_drops: {drops}";
				//Console.WriteLine(s);
			}
			bFirst = false;
			prev_fc = curr_fc;

			bExit = (nUnplayedBytes <= 0);

			if (_kbhit() && (_getch() == ESC))
			{
				bExit = true;
			}

		} while (!bExit);
		cout << drops << " drops. " << endl;

	}
	else
		std::cout << std::endl;
}

bool GetSingle()
{
	bool bRet = false;
	int32_t r;

	CFrameInfo f;

	if (r = singlepno_fxn_info[g_TrackerType].func(g_ctx, g_hnd, f, E_PNOMODE_STD))
	{
		DisplayError(r, singlepno_fxn_info[g_TrackerType].szfunc);
	}
	else
	{
		tcout << endl;
		DisplayNativeFramePNO((MYPNOTYPE *)f.pF);
	}

	return bRet;
}

// #include VPcmdIF.h
// 
//bool GetSingleN()
//{
//
//	int32_t r;
//	vpcmd_context g_ctx;
//	vpdev_hnd     g_hnd;
//	CFrameInfo f;
//	if (r = vpcmd_dev_singlepno(g_ctx, g_hnd, f))
//	{
//		// error 
//		cout << vpcmdif_errorStr(r) << endl;
//	}
//	else 
//	{
//		// handle data returned in f
//	}
//	return (r == 0);
//}

/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
typedef union
{
	uint32_t uinfo;
	HPINFO bfinfo;
}u_hpinfo;
int32_t DisplayNativeFramePNO(MYPNOTYPE * pv, uint64_t * pts/*=0*/)
{
	std::string s;
	int32_t i = 0;
	//uint64_t dur1 = 0;
	uint64_t dur2 = 0;

	if (!pv)
	{
		char szMsg[300];
		sprintf_s(szMsg, _countof(szMsg), "%s : SEUPNO *pv %p", __FUNCTION__, pv);
		s += szMsg;
		std::cout << (s.c_str()) << std::endl;
	}
	else
	{
		if (pts != 0)
		{
			std::chrono::milliseconds ts(*pts);
			//std::chrono::time_point<std::chrono::steady_clock> dt(ts);


			//dur1 = std::chrono::duration_cast<std::chrono::microseconds>(dt - g_starttime).count();
			dur2 = (*pts - g_start);

			//g_starttime = dt;
			g_start = *pts;

		}

		uint32_t	nSeuID = pv->seupno.seuid;
		uint32_t	nFrameNum = pv->seupno.frame;
		uint32_t	nSCount = pv->seupno.sensorCount;
		u_hpinfo    hpinfo;
		hpinfo.bfinfo = pv->seupno.HPinfo;

		uint32_t	nDigIO = hpinfo.uinfo;

		i += sizeof(SEUPNO_HDR);

		char szData[300];
		for (uint32_t j = 0; j<nSCount; j++)
		{
			SENFRAMEDATA *  pSD = (SENFRAMEDATA*)&(pv->sarr[j]);

			if (pSD->SFinfo.bfOriUnits == ORI_QUATERNION)
			{
				if (g_bTimestamp)
					sprintf_s(szData, _countof(szData), "0x%I64x %3d %3d |  %07d |  0x%04x |  % 7.3f % 7.3f % 7.3f | % 8.4f % 8.4f % 8.4f % 8.4f\r\n",
						dur2,
						nSeuID, pSD->SFinfo.bfSnum,
						nFrameNum, nDigIO,
						pSD->pno.pos[0], pSD->pno.pos[1], pSD->pno.pos[2],
						pSD->pno.ori[0], pSD->pno.ori[1], pSD->pno.ori[2], pSD->pno.ori[3]);
				else
					sprintf_s(szData, _countof(szData), "%3d %3d |  %07d |  0x%04x |  % 7.3f % 7.3f % 7.3f | % 8.4f % 8.4f % 8.4f % 8.4f\r\n",
						nSeuID, pSD->SFinfo.bfSnum,
						nFrameNum, nDigIO,
						pSD->pno.pos[0], pSD->pno.pos[1], pSD->pno.pos[2],
						pSD->pno.ori[0], pSD->pno.ori[1], pSD->pno.ori[2], pSD->pno.ori[3]);

			}
			else
			{
				if (g_bTimestamp)
					sprintf_s(szData, _countof(szData), "0x%I64x %3d %3d |  %07d |  0x%04x |  % 7.3f % 7.3f % 7.3f | % 8.3f % 8.3f % 8.3f\r\n",
						dur2,
						nSeuID, pSD->SFinfo.bfSnum,
						nFrameNum, nDigIO,
						pSD->pno.pos[0], pSD->pno.pos[1], pSD->pno.pos[2],
						pSD->pno.ori[0], pSD->pno.ori[1], pSD->pno.ori[2]);
				else
					sprintf_s(szData, _countof(szData), "%3d %3d |  %07d | %d %d | %03d | 0x%04x |  % 7.3f % 7.3f % 7.3f | % 8.3f % 8.3f % 8.3f\r\n",
						nSeuID, pSD->SFinfo.bfSnum,
						nFrameNum,
						pSD->SFinfo.bfBtnState0, pSD->SFinfo.bfBtnState1,
						pSD->SFinfo.bfDistortion,
						pSD->SFinfo.bfAuxInput,
						pSD->pno.pos[0], pSD->pno.pos[1], pSD->pno.pos[2],
						pSD->pno.ori[0], pSD->pno.ori[1], pSD->pno.ori[2]);

				//sprintf_s(szData, _countof(szData), "%3d %3d |  %07d |  0x%04x |  % 7.3f % 7.3f % 7.3f | % 8.3f % 8.3f % 8.3f\r\n",
				//		nSeuID, pSD->SFinfo.bfSnum,
				//		nFrameNum, 
				//		nDigIO,
				//		pSD->pno.pos[0], pSD->pno.pos[1], pSD->pno.pos[2],
				//		pSD->pno.ori[0], pSD->pno.ori[1], pSD->pno.ori[2]);

			}
			s += szData;
			i += sizeof(SENFRAMEDATA);
		}

		std::cout << s.c_str();
	}
	return i;
}

uint32_t DisplayOneRawFrame(void *pv, CVPSeuPnoL & pno)
{
	vpFrameInfo *f = (vpFrameInfo*)pv;
	if (!((f != 0) && (f->pF != 0) && (f->uiSize != 0)))
		return 0;

	//CVPSeuPnoL pno;
	uint32_t frame_bytes = pno.Extractraw(f->pF);

	if (frame_bytes)
	{
		std::string s;
		s.reserve(900);
		pno.Report(s, false, false);
		std::cout << s.c_str();
	}

	return frame_bytes;
}
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
//frame to parse does not include include preamble and size
void DisplayNativeFrame(void * pv)
{
	vpFrameInfo *f = (vpFrameInfo*)pv;

	uint8_t * pBuf = f->pF;
	int32_t iSize = f->uiSize;
	uint64_t * pTS = g_bTimestamp ? &f->ts : 0;
	std::string s;

	if ((!pBuf) || (!iSize))
	{
		char szMsg[300];
		sprintf_s(szMsg, _countof(szMsg), "%s : pBuf %p, iSize %d", __FUNCTION__, pBuf, iSize);
		s += szMsg;
		std::cout << (s.c_str()) << std::endl;
	}
	else
	{
		int32_t i = 0;
		MYPNOTYPE	* pdevFrame;

		while (i < iSize)
		{
			pdevFrame = (MYPNOTYPE*)(&pBuf[i]);

			i += DisplayNativeFramePNO((MYPNOTYPE*)&pBuf[i], pTS);

			i += sizeof(uint32_t); // crc

		} // end while isize
		std::cout << s.c_str();
	}
}

/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
CStationMap g_csmap;
//void ShowStationMapMenu(void)
//{
//	tcout << _T("\n\nEnter new enabled map (hexadecimal).\n");
//	tcout << _T("'*' or -1 Enables all stations\n");
//	tcout << _T("Type ENTER to Continue with no change\n");
//	tcout << endl;
//	tcout << _T("New Enabled Station Map Selection>> ");
//}
//bool StationMapPrompt(void)
//{
//	bool bNew = false;
//	bool bExit = false;
//	ShowStationMapMenu();
//	uint32_t new_map = 0;
//	do
//	{
//		int ch;
//		TCHAR input[20];
//		tcin.getline(input, 20);
//		if (tcslen(input) == 0) // just Enter was typed
//			ch = ENTER;
//		else
//			ch = input[0];// _getche();
//
//		switch (ch)
//		{
//		case ENTER:
//			bNew = false;
//			bExit = true;
//			tcout << _T("Selected Enabled Station Map ") << std::hex << g_csmap.en_map << _T(" unchanged.") << endl;
//			break;
//		case _T('*'):
//			new_map = -1;
//			tcout << _T("You selected ") << new_map << _T(" (All Stations).") << endl;
//			bExit = true;
//			bNew = (new_map != g_csmap.en_map);
//			g_csmap.en_map = new_map;
//			break;
//		default:
//		{
//			int32_t hin;
//			tstring strinput(input);
//			tstringstream(strinput) >> std::hex >> hin;
//
//			tcout << _T("You selected enabled map 0x") << std::hex << hin;
//			if (hin == -1)
//				tcout << _T(" (All stations).");
//
//			tcout << endl;
//			bExit = true;
//			bNew = (hin != g_csmap.en_map);
//
//			g_csmap.en_map = new_map;
//		}
//		break;
//		}
//
//	} while (bExit == false);
//
//	return bNew;
//}
void DevStationMap()
{
	int r = 0;

	if (r = vpcmd_dev_stationmap(g_ctx, g_hnd, g_csmap))
		DisplayError(r, "vpcmd_dev_stationmap ");

	else
	{
		std::string s; s.reserve(200);
		g_csmap.Report(s);
		cout << endl << s.c_str() << endl;

		//if (StationMapPrompt())
		//{
		//	r = vpcmd_dev_stationmap(g_ctx, g_hnd, g_csmap);
		//	DisplayError(r, "vpcmd_dev_stationmap CMD_ACTION_SET");
		//}
	}
}
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
CUnitsCfg g_cUnits;
void ShowOriUnitsMenu()
{
	tcout << _T("\n\nPlease select a orientation units option: \n\n");
	tcout << _T("E or e:  Output Ori Euler Angles (degrees)\n");
	tcout << _T("R or r:  Output Ori Euler Angles (radians)\n");
	tcout << _T("Q or q:  Output Ori Quaternion (default)\n");
	tcout << _T("ESC:     Exit Ori Units Menu, no change\n");
	tcout << endl;
}
bool OriUnitsPrompt(eViperOriUnits &eSelection)
{
	bool bQuit = false;
	bool bCont = true;

	ShowOriUnitsMenu();
	do
	{
		INT ch;

		tcout << _T("\nEe/Rr/Qq/ESC>> ");
		ch = _getche();
		if (ch == 0)
			ch = _getche();
		ch = toupper(ch);
		bCont = false;

		switch (ch)
		{
		case ESC:
			bQuit = true;
			break;
		case ENTER:
			ShowOriUnitsMenu();
			bCont = true;
			break;
		case 'E':
			eSelection = ORI_EULER_DEGREE;
			break;
		case 'R':
			eSelection = ORI_EULER_RADIAN;
			break;
		case 'Q':
			eSelection = ORI_QUATERNION;
			break;
		default:
			tcout << _T("\nUnsupported Selection\n");
			bCont = true;
			break;
		}

	} while (bCont == true);

	return bQuit;
}
void ShowPosUnitsMenu()
{
	tcout << _T("\n\nPlease select a position units option: \n\n");
	tcout << _T("C or c:  Output Pos CM\n");
	tcout << _T("M or m:  Output Pos Meter\n");
	tcout << _T("I or i:  Output Pos Inches (default)\n");
	tcout << _T("F or f:  Output Pos Foot\n");
	tcout << _T("ESC:     Exit Pos Units Menu, no change\n");
	tcout << endl;
}
bool PosUnitsPrompt(eViperPosUnits &eSelection)
{
	bool bQuit = false;
	bool bCont = true;

	ShowPosUnitsMenu();
	do
	{
		INT ch;

		tcout << _T("\nCc/Mm/Ii/Ff/ESC>> ");
		ch = _getche();
		if (ch == 0)
			ch = _getche();
		ch = toupper(ch);

		bCont = false;
		switch (ch)
		{
		case ENTER:
			ShowOriUnitsMenu();
			bCont = true;
			break;
		case ESC:
			bQuit = true;
			break;
		case 'C':
			eSelection = POS_CM;
			break;
		case 'M':
			eSelection = POS_METER;
			break;
		case 'I':
			eSelection = POS_INCH;
			break;
		case 'F':
			eSelection = POS_FOOT;
			break;
		default:
			bCont = true;
			tcout << _T("\nUnsupported Selection\n");
			break;
		}

	} while (bCont == true);

	return bQuit;
}
void ShowUnitsMenu()
{
	tcout << _T("\n\nPlease select a Units Configuration Option: \n\n");
	tcout << _T("R or r: Reset Pos and Ori units to default values\n");
	tcout << _T("N or n: Select new Pos and Ori units\n");
	tcout << _T("ESC:  Exit Units Configuration Menu\n");
	tcout << endl;
}
// this fcn is not my best work..
bool UnitsPrompt( eCmdActions &eact )
{
	bool bNew = false;
	bool bReset = false;
	eViperPosUnits ePosUnits = (eViperPosUnits)(g_cUnits.pos_units);
	eViperOriUnits eOriUnits = (eViperOriUnits)(g_cUnits.ori_units);
	
	ShowUnitsMenu();
	bool bCont = false;
	do
	{
		INT ch;

		tcout << _T("\nRr/Nn/ESC>> ");
		ch = _getche();
		if (ch == 0)
			ch = _getche();
		ch = toupper(ch);

		bCont = false;
		switch (ch)
		{
		case ESC:
			break;
		case 'R':
			bReset = true;
			eact = CMD_ACTION_RESET;
			break;
		case 'N':
			if (PosUnitsPrompt(ePosUnits)) // returns true if QUIT selected
			{}
			else if (OriUnitsPrompt(eOriUnits))
			{}
			else // quit not selected
			{
				tcout << _T(" ");
				//bNew = (g_units.ori_units != eOriUnits) || (g_units.pos_units != ePosUnits);
				g_cUnits.ori_units = eOriUnits;
				g_cUnits.pos_units = ePosUnits;
				bNew = true;
				eact = CMD_ACTION_SET;
			}			
			break;
		default:
			bCont = true;
			tcout << _T("\nUnsupported Selection\n");
			break;
		}

	} while (bCont == true);


	return (bReset || bNew);
}
void DevUnits()
{
	int r = 0;

	if (r = vpcmd_dev_units(g_ctx, g_hnd, CMD_ACTION_GET, g_cUnits))
		DisplayError(r, "vpcmd_dev_units CMD_ACTION_GET");

	else
	{
		std::string s; s.reserve(200);
		//CUnitsCfg cunits(&g_units);
		g_cUnits.Report(s);
		cout << endl << "Current P&O Units Configuration:" << endl << s.c_str() << endl;
		eCmdActions eAct = CMD_ACTION_SET;

		if (UnitsPrompt(eAct))
		{
			r = vpcmd_dev_units(g_ctx, g_hnd, eAct, g_cUnits);// &g_units);

			std::string s("vpcmd_dev_units ");
			s += CVPcmd::actstr(eAct);

			DisplayError(r, s.c_str());
		}
	}
}
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
CStylusCfg g_cstymode;
void ShowStyModeMenu()
{
	std::string s; s.reserve(200);
	g_cstymode.Report(s);
	tcout << _T("\nCurrent Stylus Mode is ") << s.c_str() << _T("\n");
	tcout << _T("\n\nPlease select a stylus mode option: \n\n");
	tcout << _T("M or m:  STYLUS_MODE_MARK\n");
	tcout << _T("P or p:  STYLUS_MODE_POINT\n");
	tcout << _T("L or l:  STYLUS_MODE_LINE\n");
	tcout << _T("T or t:  STYLUS_MODE_TOGGLE\n");
	tcout << _T("Type R or r to reset Stylus Mode to default mode ") << CVPcmd::stylusmodestr(STYLUS_MODE_MARK) << _T("\n");
	tcout << _T("ESC:     Exit Stylus Mode Menu, no change\n");
	tcout << endl;

}
bool StyModePrompt(eCmdActions &eact)
{
	bool bNew = false;
	bool bReset = false;

	uint32_t selection = g_cstymode.val;

	ShowStyModeMenu();
	bool bExit = false;
	do
	{
		int ch;

		tcout << _T("\nMm/Dd/Rr/ESC>> ");
		ch = _getche();
		if (ch == 0)
			ch = _getche();
		//tcout << _T("   you typed 0x") << hex << ch << endl;
		ch = toupper(ch);
		bReset = false;
		bNew = false;

		switch (ch)
		{
		case ESC:
			bExit = true;
			break;
		case ENTER:
			ShowStyModeMenu();
			break;
		case 'M':
			g_cstymode.Fill(STYLUS_MODE_MARK);
			bNew = true;
			bExit = true;
			break;
		case 'P':
			g_cstymode.Fill(STYLUS_MODE_POINT);
			bNew = true;
			bExit = true;
		case 'L':
			g_cstymode.Fill(STYLUS_MODE_LINE);
			bNew = true;
			bExit = true;
		case 'T':
			g_cstymode.Fill(STYLUS_MODE_TOGGLE);
			bNew = true;
			bExit = true;
		case 'R':
			bReset = true;
			bExit = true;
			eact = CMD_ACTION_RESET;
			break;
		case 0:
			break;
		default:
			tcout << _T("\nUnsupported Selection 0x") << hex << ch << endl;
			break;
		}
	} while (!bExit);

	return (bReset || bNew);

}
void DevStylusMode()
{
	int r = 0;

	if (r = vpcmd_dev_stylusmode(g_ctx, g_hnd, CMD_ACTION_GET, g_cstymode))
		DisplayError(r, "vpcmd_dev_stylusmode CMD_ACTION_GET");

	else
	{
		std::string s;  s.reserve(200);
		CStylusCfg cstymode(g_cstymode);
		cstymode.Report(s);
		cout << endl << "Current Stylus Mode:" << endl << s.c_str() << endl;
		eCmdActions eAct = CMD_ACTION_SET;

		if (StyModePrompt(eAct))
		{
			r = vpcmd_dev_stylusmode(g_ctx, g_hnd, eAct, g_cstymode);

			std::string s("vpcmd_dev_stylusmode ");
			s += CVPcmd::actstr(eAct);

			DisplayError(r, s.c_str());
		}
	}

}
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
void ShowFRateMenu()
{
	std::string s;  s.reserve(200);
	g_cfrate.Report(s);
	tcout << _T("\nCurrent Frame Rate is ") <<  s.c_str() << _T("\n");
	tcout << _T("\n\nPlease select a frame rate option: \n\n");
	tcout << _T("0:  FR_30\n");
	tcout << _T("1:  FR_60\n");
	tcout << _T("2:  FR_120\n");
	tcout << _T("3:  FR_240\n");
	tcout << _T("4:  FR_480\n");
	tcout << _T("5:  FR_960\n");
	tcout << _T("Type R or r to reset Frame Rate to default rate of ") << std::dec << VIPER_MAX_FRAMES_PER_SEC << _T("\n");
	tcout << _T("ESC:     Exit Frame Rate Menu, no change\n");
	tcout << endl;

}
bool FRatePrompt(eCmdActions &eact)
{
	bool bNew = false;
	bool bReset = false;

	uint32_t selection = g_cfrate.frame_rate;

	ShowFRateMenu();
	bool bExit = false;
	do
	{
		int ch;

		tcout << _T("\n0/1/2/3/4/5/Rr/ESC>> ");
		ch = _getche();
		if (ch == 0)
			ch = _getche();
		ch = toupper(ch);
		bReset = false;
		bNew = false;

		switch (ch)
		{
		case ESC:
			bExit = true;
			break;
		case ENTER:
			ShowFRateMenu();
			break;
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
			selection = ch - '0';
			g_cfrate.frame_rate = selection;
			eact = CMD_ACTION_SET;
			tcout << _T("You selected Frame Rate ") << g_cfrate.EffectiveRate() << _T(" frames/sec\r\n");
			bNew = true;
			bExit = true;
			break;
		case 'R':
			bReset = true;
			bExit = true;
			eact = CMD_ACTION_RESET;
			break;
		default:
			tcout << _T("\nUnsupported Selection\n");
			break;
		}
	} while (!bExit);

	return (bReset || bNew);
}

void DevFrameRate()
{
	int r = 0;

	if (r = vpcmd_dev_framerate(g_ctx, g_hnd, CMD_ACTION_GET, g_cfrate))
		DisplayError(r, "vpcmd_dev_framerate CMD_ACTION_GET");

	else
	{
		std::string s; s.reserve(200);
		CFrameRateCfg cfrate(g_cfrate);
		cfrate.Report(s);
		cout << endl << "Current Frame Rate Configuration:" << endl << s.c_str() << endl;
		eCmdActions eAct = CMD_ACTION_SET;

		if (FRatePrompt(eAct))
		{
			r = vpcmd_dev_framerate(g_ctx, g_hnd, eAct, g_cfrate);

			std::string s("vpcmd_dev_framerate ");
			s += CVPcmd::actstr(eAct);

			DisplayError(r, s.c_str());
		}
	}
}
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
//SEUID_CONFIG g_seuidcfg;
CSeuIDCfg g_cseuidcfg;
void ShowDevIDSelectMenu(void)
{
	tcout << _T("\nCurrent Device/SEU ID is ") << g_cseuidcfg.seuid << _T(".\n");
	tcout << _T(" Enter a new numeric SEU ID.");
	tcout << _T(" Type ENTER to continue with no change\n");
	tcout << _T("New Device/SEU Selection (+ENTER)>> ");
}
bool DevIDPrompt()
{
	bool bExit = false;
	bool bNew = false;
	ShowDevIDSelectMenu();
	do
	{
		int ch;
		TCHAR input[20];
		tcin.getline(input, 20);
		if (tcslen(input) == 0) // just Enter was typed
			ch = ENTER;
		else
			ch = input[0];// _getche();

		switch (ch)
		{
		case ENTER:
			bExit = true;
			tcout << _T("Selected SEU ID ") << g_cseuidcfg.seuid << _T(" unchanged.") << endl;
			break;
		default:
			{
				int hin;
				tstring strinput(input);
				tstringstream(strinput) >> hin;

				tcout << _T("You selected SEU ID ") << hin;

				tcout << endl;
				bExit = true;

				bNew = true; // (g_seuidcfg.seuid == hin);
				g_cseuidcfg.seuid = hin;
			}
			break;
		}

	} while (bExit == FALSE);

	return bNew;
}
void DevID()
{
	int r = 0;

	if (r = vpcmd_dev_id(g_ctx, g_hnd, CMD_ACTION_GET, g_cseuidcfg))
		DisplayError(r, "vpcmd_dev_id CMD_ACTION_GET");

	else
	{
		if (DevIDPrompt())
		{
			r = vpcmd_dev_id(g_ctx, g_hnd, CMD_ACTION_SET, g_cseuidcfg);
			DisplayError(r, "vpcmd_dev_id CMD_ACTION_SET");
		}
	}

}
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
CFilterCfg g_cfiltcfg_ori;
CFilterCfg g_cfiltcfg_pos;
void ShowFilterValuesMenu(const char *szType, bool bOptionSame, const char *szPrev)
{
	tcout << _T("\nPlease choose a new");
	if (szType)
		cout << " " << szType;
	tcout << _T(" Filter level: \n");
	tcout << _T(" 0 : FILTER_LVL_NONE\n");
	tcout << _T(" 1 : FILTER_LVL_LIGHT\n");
	tcout << _T(" 2 : FILTER_LVL_MEDIUM\n");
	tcout << _T(" 3 : FILTER_LVL_HEAVY\n");
	tcout << _T(" 4 : FILTER_LVL_CUSTOM\n");
	tcout << _T(" 5 : FILTER_LVL_E_LIGHT\n");
	tcout << _T(" 6 : FILTER_LVL_E_MEDIUM\n");
	tcout << _T(" 7 : FILTER_LVL_E_HEAVY\n");
	if (bOptionSame)
	{
		tcout << _T(" S or s: Same values as ");
		if (szPrev)
			cout << szPrev << " filter" << endl;
		else
			cout << "previous selection\r\n";
	}
	tcout << _T(" Type ENTER to continue with no change\n");
	//tcout << _T("ESC:     Exit Pos Units Menu, no change\n");
	tcout << _T(">> ");
}
CFilterCfg in_fcfg;
bool FilterValuesPrompt(FILTER_CONFIG *fcfg, const char *szType, bool bSame, const char *szPrev)
{
	bool bQuit = false;
	bool bExit = false;
	float fp_in[4];

	if (!bSame)
		in_fcfg.Init();

	bool bCustAccept = false;
	ShowFilterValuesMenu(szType, bSame, szPrev);
	do
	{
		int ch;
		ch = _getche();
		if (ch == 0)
			ch = _getche();
		ch = toupper(ch);

		int hin = (int)ch - (int)'0';
		switch (ch)
		{
		case 'S':
			if (bSame)
			{

				memcpy(fcfg, (FILTER_CONFIG*)in_fcfg, sizeof(FILTER_CONFIG));
				bExit = true;
				break;
			}
		default:
			{
				tcout << _T("Invalid selection\n\n");
				bCustAccept = false;
				ShowFilterValuesMenu(szType, bSame, szPrev);
			}
			break;
		case ENTER:
			bExit = true;
			if (bCustAccept)
			{
				in_fcfg.Fill(FILTER_LVL_CUSTOM, fp_in);
				memcpy(fcfg, (FILTER_CONFIG*)in_fcfg, sizeof(FILTER_CONFIG));
			}
			else // no change selected
			{
				if (szType)
					cout << szType << " ";
				cout << "filter will not be changed" << endl;
				in_fcfg = fcfg;
				bQuit = true;
			}
			break;
		case ESC:
			if (szType)
				cout << szType << " ";
			cout << "filter will not be changed" << endl;
			bExit = true;
			bQuit = true;
			break;
		case '4':
			{
				cout << "\n  You selected";
				if (szType)
					cout << " " << szType;
				cout << " Filter Level " << hin << " : " << CVPcmd::filterlevstr(hin) << endl;
			
				int argcount = 0;
				while (argcount < 4)
				{
					tcout << _T("\n\nSelect Custom Filter values: F, Flow, Fhigh and MaxT\r\n");
					tcout << _T("\nEnter 4 comma-separated floating-point values (e.g. 0.2, 0.2, 0.8, 0.95) >> ");
					TCHAR finput[100];
					tcin.getline(finput, 100);// , _T(','));
					argcount = _stscanf_s(finput, _T("%f,%f,%f,%f"), &fp_in[0], &fp_in[1], &fp_in[2], &fp_in[3]);
				}
				tcout << _T("You entered F=") << fp_in[0] << _T(", Flow=") << fp_in[1] << _T(", Fhigh=") << fp_in[2] << _T(", MaxT=") << fp_in[3] << endl;
				tcout << _T("Press Enter to continue, ESC to quit, C or c to repeat>> \n");
				tcout << _T("ENTER//ESC/Cc>> ");
				bCustAccept = true;
			}
			break;
		case '0':
		case '1':
		case '2':
		case '3':
		case '5':
		case '6':
		case '7':
		{
				int hin = (int)ch - (int)'0';
				//tstring strinput(input);
				//tstringstream(strinput) >> hin;

				cout << "\n  You selected";
				if (szType)
					cout << " " << szType;
				cout << " Filter Level "<< hin << " : " << CVPcmd::filterlevstr(hin) << endl;

				bExit = true;
				in_fcfg.Fill((eViperFilterLevel)hin);
				memcpy(fcfg, (FILTER_CONFIG*)in_fcfg, sizeof(FILTER_CONFIG));
			}
			break;
		}

	} while (!bExit);

	return bQuit;
}
void ShowFilterTargetsMenu()
{
	tcout << _T("\nPlease select Filter Target: \n");
	tcout << _T(" P or p : Position only\n");
	tcout << _T(" O or o : Orietation only\n");
	tcout << _T(" B or b : Both (PNO)\n");
	tcout << _T(" ESC    : Exit Filter Target Menu, no selection\n");
	tcout << _T(">> ");
	//tcout << endl;
}
void ShowFilterMenu()
{
	tcout << _T("\nPlease select a Filter Configuration Option: \n");
	tcout << _T(" R or r: Reset Pos and Ori Filters to default values\n");
	tcout << _T(" N or n: Select new Pos and Ori Filter values\n");
	tcout << _T(" ESC:  Exit Filter Configuration Menu\n");
	//tcout << endl;
}
bool FilterPrompt(eCmdActions &eact, bool & bPos, bool & bOri)
{
	bool bNew = false;
	bool bReset = false;
	bool bExit = false;
	bool bBoth = false;
	bPos = true;
	bOri = true;

	ShowFilterMenu();
	do
	{
		INT ch;

		tcout << _T("\nRr/Nn/ESC>> ");
		ch = _getche();
		if (ch == 0)
			ch = _getche();
		ch = toupper(ch);

		switch (ch)
		{
		case ESC:
			bPos = false;
			bOri = false;
			bExit = true;
			break;
		case 'R':
			bReset = true;
			bPos = true;
			bOri = true;
			eact = CMD_ACTION_RESET;
			bExit = true;
			break;
		case 'N':
			bPos = true;
			bOri = true;
			//tcout << _T("\n\nPosition Filter:\r\n");
			if (FilterValuesPrompt(g_cfiltcfg_pos, "POSITION", false, 0)) // returns true if QUIT selected
			{
				//quit selected
				bPos = false;
			}
			//tcout << _T("\n\nOrientation Filter:\r\n");
			if (FilterValuesPrompt(g_cfiltcfg_ori, "ORIENTATION", true, "POSITION"))
			{
				bOri = false;
			}
			bNew = (bPos | bOri);
			eact = CMD_ACTION_SET;
			bExit = true;
			break;
		default:
			tcout << _T("\nUnsupported Selection\n");
			break;
		}

	} while (!bExit);


	return (bReset || bNew);
}
void SensorFilter(int sns)
{
	int r = 0;

	if (r = vpcmd_sns_filter(g_ctx, g_hnd, sns, CMD_ACTION_GET, g_cfiltcfg_pos, FILTER_TRGT_POS))
		DisplayError(r, "vpcmd_sns_filter CMD_ACTION_GET FILTER_TRGT_POS");

	else if (r = vpcmd_sns_filter(g_ctx, g_hnd, sns, CMD_ACTION_GET, g_cfiltcfg_ori, FILTER_TRGT_ORI))
		DisplayError(r, "vpcmd_sns_filter CMD_ACTION_GET FILTER_TRGT_ORI");

	else
	{
		std::string s;  s.reserve(200);
		cout << "Sensor " << sns << " Position Filter Configuration:" << endl;
		g_cfiltcfg_pos.Report(s);
		cout << s.c_str() << endl;
		cout << "Sensor " << sns << " Orientation Filter Configuration:" << endl;
		s.clear();
		g_cfiltcfg_ori.Report(s);
		cout << s.c_str() << endl;

		eCmdActions eAct = CMD_ACTION_SET;
		bool bPos = false;
		bool bOri = false;

		if (FilterPrompt(eAct, bPos, bOri))
		{
			std::string s("vpcmd_sns_filter ");
			s += CVPcmd::actstr(eAct); s += " ";

			if (bPos && bOri && ((eAct == CMD_ACTION_RESET) || (g_cfiltcfg_pos == g_cfiltcfg_ori)))
			{
				r = vpcmd_sns_filter(g_ctx, g_hnd, sns, eAct, g_cfiltcfg_pos, FILTER_TRGT_PNO);
				s += CVPcmd::filtertarstr(FILTER_TRGT_PNO);
				DisplayError(r, s.c_str());
			}
			else
			{
				if (bPos)
				{
					r = vpcmd_sns_filter(g_ctx, g_hnd, sns, eAct, g_cfiltcfg_pos, FILTER_TRGT_POS);
					s += CVPcmd::filtertarstr(FILTER_TRGT_POS);
					DisplayError(r, s.c_str());
				}

				if (bOri)
				{
					r = vpcmd_sns_filter(g_ctx, g_hnd, sns, eAct, g_cfiltcfg_ori, FILTER_TRGT_ORI);
					s += CVPcmd::filtertarstr(FILTER_TRGT_ORI);
					DisplayError(r, s.c_str());
				}
			}
		}
	}
}
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
CFTTCfg g_cFTTcfg;
void ShowFTTValuesMenu()
{
	tcout << _T("\nPlease choose a new FTT mode:\n");
	tcout << _T(" 0 : FTT_MODE_NONE\n");
	tcout << _T(" 1 : FTT_MODE_0\n");
	tcout << _T(" 2 : FTT_MODE_1\n");
	tcout << _T(" 3 : FTT_MODE_2\n");
	tcout << _T(" Type ENTER to continue with no change\n");
	tcout << _T(">> ");
}
CFTTCfg in_fttcfg;
bool FTTValuesPrompt(FTTMODE_CONFIG *fcfg )
{
	bool bQuit = false;
	bool bExit = false;

	in_fttcfg.Init();
	ShowFTTValuesMenu();
	do
	{
		int ch;
		ch = _getche();
		if (ch == 0)
			ch = _getche();
		ch = toupper(ch);

		switch (ch)
		{
		default:
		{
			tcout << _T("Invalid selection\n\n");
			ShowFTTValuesMenu();
		}
		break;
		case ENTER:
		case ESC:
			bExit = true;
			cout << "FTT mode will not be changed" << endl;
			bQuit = true;
			break;
		case '0':
		case '1':
		case '2':
		case '3':
		{
			int hin = (int)ch - (int)'0';
			//tstring strinput(input);
			//tstringstream(strinput) >> hin;

			cout << "\n  You selected FTT Mode ";
			cout <<  hin << " : " << CVPcmd::fttstr(hin) << endl;

			bExit = true;
			in_fttcfg.Fill((eFTTMode)hin);
			memcpy(fcfg, (FTTMODE_CONFIG*)in_fttcfg, sizeof(FTTMODE_CONFIG));
		}
		break;
		}

	} while (!bExit);

	return bQuit;
}
void ShowFTTMenu()
{
	tcout << _T("\nPlease select a FTT mode Configuration Option: \n");
	tcout << _T(" R or r: Reset FTT mode to default values\n");
	tcout << _T(" N or n: Select FTT mode\n");
	tcout << _T(" ESC:  Exit FTT mode Configuration Menu\n");
	//tcout << endl;
}
bool FTTPrompt(eCmdActions &eact)
{
	bool bNew = false;
	bool bReset = false;
	bool bExit = false;
	bool bBoth = false;

	ShowFTTMenu();
	do
	{
		INT ch;

		tcout << _T("\nRr/Nn/ESC>> ");
		ch = _getche();
		if (ch == 0)
			ch = _getche();
		ch = toupper(ch);

		switch (ch)
		{
		case ESC:
			bExit = true;
			break;
		case 'R':
			bReset = true;
			eact = CMD_ACTION_RESET;
			bExit = true;
			break;
		case 'N':
			//tcout << _T("\n\nPosition Filter:\r\n");
			if (FTTValuesPrompt(g_cFTTcfg)) // returns true if QUIT selected
			{
				//quit selected
				bNew = false;
			}
			else
				bNew = true;
			eact = CMD_ACTION_SET;
			bExit = true;
			break;
		default:
			tcout << _T("\nUnsupported Selection\n");
			break;
		}

	} while (!bExit);


	return (bReset || bNew);
}

void SensorFTT(int sns)
{
	int r = 0;

	if (r = vpcmd_sns_fttmode(g_ctx, g_hnd, sns, CMD_ACTION_GET, g_cFTTcfg))
		DisplayError(r, "vpcmd_sns_fttmode CMD_ACTION_GET ");

	else
	{
		std::string s; s.reserve(200);
		cout << "Sensor " << sns << " FTT Mode:" << endl;
		g_cFTTcfg.Report(s);
		cout << s.c_str() << endl;
		s.clear();

		eCmdActions eAct = CMD_ACTION_SET;
		bool bPos = false;
		bool bOri = false;

		if (FTTPrompt(eAct))
		{
			std::string s("vpcmd_sns_fttmode ");
			s += CVPcmd::actstr(eAct); s += " ";

			r = vpcmd_sns_fttmode(g_ctx, g_hnd, sns, eAct, g_cFTTcfg);
			DisplayError(r, s.c_str());
		}
	}
}

void SetTraceLev(int nTraceLev)
{
	g_TraceLevel = nTraceLev;
	g_bTraceMode = (g_TraceLevel != 0);

	vpctx_trace(g_ctx, nTraceLev);
	vpcmdif_trace(nTraceLev);
}
void ToggleTraceMode()
{
	g_bTraceMode = !g_bTraceMode;

	if ((g_bTraceMode) && (g_TraceLevel == 0))
	{
		g_TraceLevel = VPCMD_TRACE_3_NORMALIO;
	}

	vpctx_trace(g_ctx, g_bTraceMode ? g_TraceLevel : 0); //sets level for the context and below
	vpcmdif_trace(g_bTraceMode ? g_TraceLevel : 0);		//enables/disables for everything

	cout << "Trace Mode " << (g_bTraceMode ? "Enabled" : "Disabled") << " Level " << g_TraceLevel << endl << endl;
}
void ToggleTimeStamp()
{
	g_bTimestamp = !g_bTimestamp;
	int r = 0;
	r = vpdev_tsenable(g_ctx, g_hnd, g_bTimestamp);
	DisplayError(r, "vpdev_tsenable  ");

	if (g_bTimestamp)
	{
		g_starttime = std::chrono::steady_clock::now();
		//auto now_micro = std::chrono::time_point_cast<std::chrono::microseconds>(g_starttime);
		auto value = g_starttime.time_since_epoch();
		g_start = value.count();
	}

}
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
CTipoffCfg g_ctipoff;
void ShowTipoffValuesMenu()
{
	tcout << _T("\nEnter 3 comma-separated (x, y, z) floating-point values (e.g. 0.2, 0.2, 0.8)\r\n.");
	//tcout << _T(" Type ENTER to continue with no change\n");
	tcout << _T(">> ");
}
bool TipoffValuesPrompt(TIP_OFFSET_CONFIG *tcfg)
{
	bool bQuit = false;
	bool bExit = false;
	float to_in[3];

	ShowTipoffValuesMenu();
	do
	{
		int argcount = 0;
		while (argcount < 3)
		{
			TCHAR finput[100];
			tcin.getline(finput, 100);// , _T(','));
			argcount = _stscanf_s(finput, _T("%f,%f,%f"), &to_in[0], &to_in[1], &to_in[2]);
		}
		CTipoffCfg tmp(to_in);
		std::string s;  s.reserve(200);
		tmp.Report(s);

		tcout << _T("You entered Tip Offset ") << s.c_str() << endl;
		tcout << _T("Press Enter to continue, ESC to quit, C or c to repeat>> \n");
		tcout << _T("ENTER//ESC/Cc>> ");

		int ch;
		ch = _getche();
		if (ch == 0)
			ch = _getche();
		ch = toupper(ch);
		switch (ch)
		{
		default:
			{
				tcout << _T("Invalid selection\n\n");
				ShowTipoffValuesMenu();
			}
			break;
		case ENTER:
			bExit = true;
			memcpy(tcfg, (TIP_OFFSET_CONFIG*)tmp, sizeof(TIP_OFFSET_CONFIG));
			bQuit = false;
			break;
		case ESC:
			cout << "Tip Offset will not be changed" << endl;
			bQuit = true;
			bExit = true;
			break;
		}

	} while (!bExit);

	return bQuit;
}
void ShowTipoffMenu()
{
	tcout << _T("\nPlease select a Tip Offset Configuration Option: \n");
	tcout << _T(" R or r: Reset Tip Offset to default values\n");
	tcout << _T(" N or n: Select Tip Offset values\n");
	tcout << _T(" ESC:  Exit Tip Offset Configuration Menu\n");
	//tcout << endl;
}
bool TipoffPrompt(eCmdActions &eact)
{
	bool bNew = false;
	bool bReset = false;
	bool bExit = false;
	bool bBoth = false;

	ShowTipoffMenu();
	do
	{
		INT ch;

		tcout << _T("\nRr/Nn/ESC>> ");
		ch = _getche();
		if (ch == 0)
			ch = _getche();
		ch = toupper(ch);

		switch (ch)
		{
		case ESC:
			bExit = true;
			break;
		case 'R':
			bReset = true;
			eact = CMD_ACTION_RESET;
			bExit = true;
			break;
		case 'N':
			if (TipoffValuesPrompt(g_ctipoff)) // returns true if QUIT selected
			{
				bNew = false;
			}
			else
				bNew = true;
			eact = CMD_ACTION_SET;
			bExit = true;
			break;
		default:
			tcout << _T("\nUnsupported Selection: ") << dec << ch << endl;
			break;
		}

	} while (!bExit);


	return (bNew|bReset);
}
void SensorTipOffset(int sns)
{
	int r = 0;

	if (r = vpcmd_sns_tipoffset(g_ctx, g_hnd, sns, CMD_ACTION_GET, g_ctipoff))
		DisplayError(r, "vpcmd_sns_tipoffset CMD_ACTION_GET ");

	else
	{
		std::string s;  s.reserve(200);
		cout << "Sensor " << sns << " Tip Offset:" << endl;
		g_ctipoff.Report(s);
		cout << s.c_str() << endl;
		s.clear();

		eCmdActions eAct = CMD_ACTION_SET;

		if (TipoffPrompt(eAct))
		{
			std::string s("vpcmd_sns_tipoffset ");
			s += CVPcmd::actstr(eAct); s += " ";

			r = vpcmd_sns_tipoffset(g_ctx, g_hnd, sns, eAct, g_ctipoff);
			DisplayError(r, s.c_str());
		}
	}
}

/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
CIncrementCfg g_cincr;
void ShowIncrValuesMenu()
{
	tcout << _T("\nEnter 2 comma-separated floating-point values (e.g. 0.2,0.3) for Position and Orientation Threshold values.\r\n");
	//tcout << _T(" Type ENTER to continue with no change\n");
	tcout << _T(">> ");
}
bool IncrValuesPrompt(INCREMENT_CONFIG *tcfg)
{
	bool bQuit = false;
	bool bExit = false;
	float to_in[2];

	ShowIncrValuesMenu();
	do
	{
		int argcount = 0;
		while (argcount < 2)
		{
			TCHAR finput[100];
			tcin.getline(finput, 100);// , _T(','));
			argcount = _stscanf_s(finput, _T("%f , %f"), &to_in[0], &to_in[1]);
		}
		CIncrementCfg tmp(to_in);
		std::string s;  s.reserve(200);
		tmp.Report(s);

		tcout << _T("You entered Increment Values ") << endl << s.c_str() << endl;
		tcout << _T("Press Enter to continue, ESC to quit, C or c to repeat>> \n");
		tcout << _T("ENTER//ESC/Cc>> ");

		int ch;
		ch = _getche();
		if (ch == 0)
			ch = _getche();
		ch = toupper(ch);
		switch (ch)
		{
		default:
		{
			tcout << _T(" Invalid selection\n\n");
			ShowIncrValuesMenu();
		}
		break;
		case 'C':
			ShowIncrValuesMenu();
			break;
		case ENTER:
			bExit = true;
			memcpy(tcfg, (INCREMENT_CONFIG*)tmp, sizeof(INCREMENT_CONFIG));
			bQuit = false;
			break;
		case ESC:
			cout << "Increment values will not be changed" << endl;
			bQuit = true;
			bExit = true;
			break;
		}

	} while (!bExit);

	return bQuit;
}
void ShowIncrMenu()
{
	tcout << _T("\nPlease select an Increment Configuration Option: \n");
	tcout << _T(" R or r: Reset Increment to default values\n");
	tcout << _T(" N or n: Select new Increment values\n");
	tcout << _T(" ESC:  Exit Increment Configuration Menu\n");
	//tcout << endl;
}
bool IncrementPrompt(eCmdActions &eact)
{
	bool bNew = false;
	bool bReset = false;
	bool bExit = false;
	bool bBoth = false;

	ShowIncrMenu();
	do
	{
		INT ch;

		tcout << _T("\nRr/Nn/ESC>> ");
		ch = _getche();
		if (ch == 0)
			ch = _getche();
		ch = toupper(ch);

		switch (ch)
		{
		case ESC:
			bExit = true;
			break;
		case 'R':
			bReset = true;
			eact = CMD_ACTION_RESET;
			bExit = true;
			break;
		case 'N':
			if (IncrValuesPrompt(g_cincr)) // returns true if QUIT selected
			{
				bNew = false;
			}
			else
				bNew = true;
			eact = CMD_ACTION_SET;
			bExit = true;
			break;
		default:
			tcout << _T("\nUnsupported Selection\n");
			break;
		}

	} while (!bExit);


	return (bNew | bReset);
}
void SensorIncrement(int sns)
{
	int r = 0;

	if (r = vpcmd_sns_increment(g_ctx, g_hnd, sns, CMD_ACTION_GET, g_cincr))
		DisplayError(r, "vpcmd_sns_increment CMD_ACTION_GET ");

	else
	{
		std::string s;  s.reserve(200);
		cout << "Sensor " << sns << " Increment:" << endl;
		g_cincr.Report(s);
		cout << s.c_str() << endl;
		s.clear();

		eCmdActions eAct = CMD_ACTION_SET;

		if (IncrementPrompt(eAct))
		{
			std::string s("vpcmd_sns_increment ");
			s += CVPcmd::actstr(eAct); s += " ";

			r = vpcmd_sns_increment(g_ctx, g_hnd, sns, eAct, g_cincr);
			DisplayError(r, s.c_str());
		}
	}
}
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
CHemisphereCfg g_chem;
void ShowHemValuesMenu()
{
	tcout << _T("\nPlease choose a new Hemisphere value.  Type: \n");
	tcout << _T(" A or a : To enable Auto-Hemisphere \n");
	tcout << _T(" T or t : To enable Hemisphere Tracking \n");
	tcout << _T(" or select a hemisphere by typing \n");
	tcout << _T(" 0 : POS_X\n");
	tcout << _T(" 1 : NEG_X\n");
	tcout << _T(" 2 : POS_Y\n");
	tcout << _T(" 3 : NEG_Y\n");
	tcout << _T(" 4 : POS_Z\n");
	tcout << _T(" 5 : NEG_Z\n");
	tcout << _T(" Type ENTER to continue with no change\n");
	//tcout << _T("ESC:     Exit Pos Units Menu, no change\n");
	tcout << _T(">> ");
}
CHemisphereCfg in_hcfg;
bool HemValuesPrompt(HEMISPHERE_CONFIG *hcfg)
{
	bool bQuit = false;
	bool bExit = false;
	in_hcfg.Init();
	memset(in_hcfg, 0, in_hcfg.Size());

	ShowHemValuesMenu();
	do
	{
		CHemisphereCfg tmp;
		std::string s; s.reserve(200);
		tmp.Report(s); 

		int ch;
		ch = _getche();
		if (ch == 0)
			ch = _getche();
		ch = toupper(ch);
		uint32_t hin = 0;
		switch (ch)
		{
		default:
		{
			tcout << _T(" Invalid selection\n\n");
			ShowHemValuesMenu();
		}
		break;
		case ENTER:
			bExit = true;
			cout << "Hemisphere setting will not be changed" << endl;
			in_hcfg = hcfg;
			bQuit = true;
			break;
		case ESC:
			cout << "Hemisphere setting will not be changed" << endl;
			bQuit = true;
			bExit = true;
			break;
		case 'A':
			//hin = CHemisphereCfg::NEG_X;
			in_hcfg = g_chem;
			in_hcfg.SetAutoEn(true);
			bExit = true;
			break;
		case 'T':
			in_hcfg = g_chem;
			in_hcfg.SetTrackEn(true);
			bExit = true;
			break;
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
			hin = (int)ch - (int)'0';
			in_hcfg.Fill((CHemisphereCfg::eHemPresetVal)hin);
			bExit = true;
			break;
		}

	} while (!bExit);

	if (!bQuit)
	{
		cout << "\n  You selected";
		cout << " Hemipshere setting " << in_hcfg.eVal << " : " << CVPcmd::hemispherestr(in_hcfg.eVal);
		if (in_hcfg.AutoEn())
			cout << " Auto ";
		if (in_hcfg.TrackEn())
			cout << " Tracking ";
		cout << endl;
		memcpy(hcfg, (HEMISPHERE_CONFIG*)in_hcfg, sizeof(HEMISPHERE_CONFIG));
	}

	return bQuit;
}
void ShowHemMenu()
{
	tcout << _T("\nPlease select a Hemisphere Configuration Option: \n");
	tcout << _T(" R or r: Reset Hemisphere to default values\n");
	tcout << _T(" N or n: Select new Hemisphere values\n");
	tcout << _T(" ESC:  Exit Hemisphere Configuration Menu\n");
	//tcout << endl;
}
bool HemispherePrompt(eCmdActions &eact)
{
	bool bNew = false;
	bool bReset = false;
	bool bExit = false;
	bool bBoth = false;

	ShowHemMenu();
	do
	{
		INT ch;

		tcout << _T("\nRr/Nn/ESC>> ");
		ch = _getche();
		if (ch == 0)
			ch = _getche();
		ch = toupper(ch);

		switch (ch)
		{
		case ESC:
			bExit = true;
			break;
		case 'R':
			bReset = true;
			eact = CMD_ACTION_RESET;
			bExit = true;
			break;
		case 'N':
			if (HemValuesPrompt(g_chem)) // returns true if QUIT selected
			{
				bNew = false;
			}
			else
				bNew = true;
			eact = CMD_ACTION_SET;
			bExit = true;
			break;
		default:
			tcout << _T("\nUnsupported Selection\n");
			break;
		}

	} while (!bExit);


	return (bNew | bReset);
}
void SensorHemisphere(int sns)
{
	int r = 0;

	if (r = vpcmd_sns_hemisphere(g_ctx, g_hnd, sns, CMD_ACTION_GET, g_chem))
		DisplayError(r, "vpcmd_sns_hemisphere CMD_ACTION_GET ");

	else
	{
		std::string s; s.reserve(200);
		cout << "Sensor " << sns << " Hemisphere:" << endl;
		g_chem.Report(s);
		cout << s.c_str() << endl;
		s.clear();

		eCmdActions eAct = CMD_ACTION_SET;

		if (HemispherePrompt(eAct))
		{
			std::string s("vpcmd_sns_hemisphere ");
			s += CVPcmd::actstr(eAct); s += " ";

			r = vpcmd_sns_hemisphere(g_ctx, g_hnd, sns, eAct, g_chem);
			DisplayError(r, s.c_str());
		}
	}
}

/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
CBoresightCfg g_cbore;
void ShowBoreValuesMenu( eViperOriUnits eU )
{
	tcout << "Boresight angles are specified as " << CVPcmd::oriunitsstr(eU) << "." << endl;
	if (eU == ORI_QUATERNION)
		tcout << "Enter 4 comma-separated floating-point values (e.g. 1.0, 0.0, 0.0, 0.0) for Boresight Reference Quaternion." << endl;
	else
		tcout << "Enter 3 comma-separated floating-point values (e.g. 0.0, 0.0, 0.0) for Boresight Reference Angles." << endl;
	tcout << _T(">> ");
}
bool BoreValuesPrompt(CBoresightCfg & bcfg)
{
	bool bQuit = false;
	bool bExit = false;
	float to_in[4];

	int args_needed = bcfg.Units() == ORI_QUATERNION ? 4 : 3;

	ShowBoreValuesMenu(bcfg.Units());
	do
	{
		int argcount = 0;
		while (argcount < args_needed)
		{
			TCHAR finput[100];
			tcin.getline(finput, 100);// , _T(','));
			argcount = _stscanf_s(finput, _T("%f , %f , %f , %f"), &to_in[0], &to_in[1], &to_in[2], &to_in[3]);
		}
		CBoresightCfg tmp(to_in);
		tmp.Units() = bcfg.Units();
		std::string s; s.reserve(200);
		tmp.Report(s);

		tcout << _T("You entered Boresight Reference Angle Values ") << endl << s.c_str() << endl;
		tcout << _T("Press Enter to continue, ESC to quit, C or c to repeat>> \n");
		tcout << _T("ENTER//ESC/Cc>> ");

		int ch;
		ch = _getche();
		if (ch == 0)
			ch = _getche();
		ch = toupper(ch);
		switch (ch)
		{
		default:
		{
			tcout << _T(" Invalid selection\n\n");
			ShowBoreValuesMenu(bcfg.Units());
		}
		break;
		case 'C':
			ShowBoreValuesMenu(bcfg.Units());
			break;
		case ENTER:
			bExit = true;
			memcpy((BORESIGHT_CONFIG*)bcfg, (BORESIGHT_CONFIG*)tmp, sizeof(BORESIGHT_CONFIG));
			bQuit = false;
			break;
		case ESC:
			cout << "Boresight values will not be changed" << endl;
			bQuit = true;
			bExit = true;
			break;
		}

	} while (!bExit);

	return bQuit;
}
void ShowBoreMenu()
{
	tcout << _T("\nPlease select an Boresight Configuration Option: \n");
	tcout << _T(" R or r: Reset/Clear Boresight\n");
	tcout << _T(" N or n: Activate Boresight with new Ref angles\n");
	tcout << _T(" ESC:  Exit Boresight Configuration Menu\n");
	//tcout << endl;
}
bool BoresightPrompt(eCmdActions &eact)
{
	bool bNew = false;
	bool bReset = false;
	bool bExit = false;
	bool bBoth = false;

	ShowBoreMenu();
	do
	{
		INT ch;

		tcout << _T("\nRr/Nn/ESC>> ");
		ch = _getche();
		if (ch == 0)
			ch = _getche();
		ch = toupper(ch);

		switch (ch)
		{
		case ESC:
			bExit = true;
			break;
		case 'R':
			bReset = true;
			eact = CMD_ACTION_RESET;
			bExit = true;
			break;
		case 'N':
			if (BoreValuesPrompt(g_cbore)) // returns true if QUIT selected
			{
				bNew = false;
			}
			else
				bNew = true;
			eact = CMD_ACTION_SET;
			bExit = true;
			break;
		default:
			tcout << _T("\nUnsupported Selection\n");
			break;
		}

	} while (!bExit);


	return (bNew | bReset);
}
void SensorBoresight(int sns)
{
	int r = 0;

	if (r = vpcmd_dev_units(g_ctx, g_hnd, CMD_ACTION_GET, &g_cUnits))
		DisplayError(r, "vpcmd_dev_units CMD_ACTION_GET");

	else if (r = vpcmd_sns_boresight(g_ctx, g_hnd, sns, CMD_ACTION_GET, g_cbore))
		DisplayError(r, "vpcmd_sns_boresight CMD_ACTION_GET ");

	else
	{
		std::string s; s.reserve(200);
		cout << "Sensor " << sns << " Boresight Rotation Angles:" << endl;
		g_cbore.Units() = (eViperOriUnits)g_cUnits.ori_units;
		g_cbore.Report(s);
		cout << s.c_str() << endl;
		cout << "Boresight is ";
		if (!(g_cbore.Enabled()))
			cout << "NOT ";
		cout << "Enabled." << endl;
		s.clear();

		eCmdActions eAct = CMD_ACTION_SET;
		if (BoresightPrompt(eAct))
		{
			std::string s("vpcmd_sns_boresight ");
			s += CVPcmd::actstr(eAct); s += " ";

			r = vpcmd_sns_boresight(g_ctx, g_hnd, sns, eAct, g_cbore);
			DisplayError(r, s.c_str());
		}
	}
}
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
void ShowPersistMenu()
{	
	cout << "\n\rThe CMD_PERSIST command will save the current values of the following Persistable tracker settings:" << endl;
	cout << " " << CVPcmd::cmdstr(CMD_HEMISPHERE)	<< endl;
	cout << " " << CVPcmd::cmdstr(CMD_FILTER)		<< endl;
	cout << " " << CVPcmd::cmdstr(CMD_TIP_OFFSET)	<< endl;
	cout << " " << CVPcmd::cmdstr(CMD_STYLUS)		<< endl;
	cout << " " << CVPcmd::cmdstr(CMD_INCREMENT)	<< endl;
	cout << " " << CVPcmd::cmdstr(CMD_FRAMERATE)	<< endl;
	cout << " " << CVPcmd::cmdstr(CMD_UNITS)		<< endl;
	cout << " " << CVPcmd::cmdstr(CMD_SOURCE_CFG)	<< endl;
	cout << " " << CVPcmd::cmdstr(CMD_SYNC_MODE)	<< endl;
	cout << " " << CVPcmd::cmdstr(CMD_STATION_MAP)	<< endl;
	cout << " " << CVPcmd::cmdstr(CMD_SEUID)		<< endl;
	cout << " " << CVPcmd::cmdstr(CMD_DUAL_OUTPUT) << endl;
	cout << " " << CVPcmd::cmdstr(CMD_SERIAL_CONFIG) << endl;
	cout << " " << CVPcmd::cmdstr(CMD_BLOCK_CFG)	<< endl << endl;
	cout << "ENTER to Continue." << endl;
	cout << "ESC   to Cancel." << endl;
	//cout << " >> ";
}
bool PersistPrompt()
{
	bool bProceed = false;
	bool bExit = false;

	ShowPersistMenu();
	do
	{
		INT ch;

		cout << " >> ";
		ch = _getche();
		if (ch == 0)
			ch = _getche();
		ch = toupper(ch);

		switch (ch)
		{
		case ENTER:
			bExit = true;
			bProceed = true;
			break;
		case ESC:
			cout << " Persist Operation cancelled " << endl;
			bExit = true;
			bProceed = false;
			break;
		default:
			tcout << _T("\nUnsupported Selection\n");
			break;
		}

	} while (!bExit);

	return bProceed;

}
void DevConfigPersist()
{
	if (PersistPrompt())
	{
		int r = 0;

		r = vpcmd_dev_persist(g_ctx, g_hnd);
		DisplayError(r, "vpcmd_dev_persist");
	}
}
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
CSrcCfg g_csrccfg[SOURCES_PER_SEU];
void ShowSrcCfgValuesMenu(int src, CSrcCfg & c )
{
	std::string s; s.reserve(200);
	c.Units().ShortReport(s);
	tcout << "Source Index " << src << ", Freq " << c.srcfreq << " Position and Rotation are specified in units ( " << s.c_str() << " )." << endl;
	if (c.Units().eOri() == ORI_QUATERNION)
		tcout << "Enter 7 comma-separated floating-point values (e.g. 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0) for Source Position and Rotation Quaternion." << endl;
	else
		tcout << "Enter 6 comma-separated floating-point values (e.g. 0.0, 0.0, 0.0, 0.0, 0.0, 0.0) for Source Position and Rotation Angles." << endl;
	tcout << _T(">> ");
}
bool SrcCfgValuesPrompt(int src, CSrcCfg & bcfg)
{
	bool bQuit = false;
	bool bExit = false;
	float to_in[7];

	int args_needed = (bcfg.Units().eOri() == ORI_QUATERNION) ? 4 : 3;

	ShowSrcCfgValuesMenu(src, bcfg);
	do
	{
		int argcount = 0;
		TCHAR finput[100];
		while (argcount < args_needed)
		{
			tcin.getline(finput, 100);// , _T(','));
			argcount = _stscanf_s(finput, _T("%f , %f , %f , %f , %f , %f , %f"), 
				&to_in[0], &to_in[1], &to_in[2], &to_in[3], &to_in[4], &to_in[5], &to_in[6] );
		}
		CSrcCfg tmp(to_in);
		tmp.Units() = bcfg.Units();
		std::string s; s.reserve(200);
		tmp.Report(s);

		tcout << _T("You entered Source Configuration Values ") << endl << s.c_str() << endl;
		tcout << _T("Press Enter to continue, ESC to quit, C or c to repeat>> \n");
		tcout << _T("ENTER//ESC/Cc>> ");

		int ch;
		ch = _getche();
		if (ch == 0)
			ch = _getche();
		ch = toupper(ch);
		switch (ch)
		{
		case 0:
			tcout << _T("0 input") << endl;
			break;
		default:
		{
			tcout << _T(" Invalid selection\n\n");
			ShowSrcCfgValuesMenu(src, bcfg);
		}
		break;
		case 'C':
			ShowSrcCfgValuesMenu(src, bcfg);
			break;
		case ENTER:
			bExit = true;
			memcpy((SRC_CONFIG*)bcfg, (SRC_CONFIG*)tmp, sizeof(SRC_CONFIG));
			bQuit = false;
			break;
		case ESC:
			cout << "Source Config values will not be changed" << endl;
			bQuit = true;
			bExit = true;
			break;
		}

	} while (!bExit);

	return bQuit;
}
void ShowSrcCfgMenu()
{
	tcout << _T("\nPlease select an Source Configuration Option: \n");
	tcout << _T(" R or r: Reset/Clear Source Configuration\n");
	tcout << _T(" N or n: Select Source Position and Rotation\n");
	tcout << _T(" ESC:  Exit Source Configuration Menu\n");
	//tcout << endl;
}
bool SrcCfgPrompt(int src, eCmdActions &eact)
{
	bool bNew = false;
	bool bReset = false;
	bool bExit = false;
	bool bBoth = false;

	ShowSrcCfgMenu();
	do
	{
		INT ch;

		tcout << _T("\nRr/Nn/ESC>> ");
		ch = _getche();
		if (ch == 0)
			ch = _getche();
		ch = toupper(ch);

		switch (ch)
		{
		case ESC:
			bExit = true;
			break;
		case 'R':
			bReset = true;
			eact = CMD_ACTION_RESET;
			bExit = true;
			break;
		case 'N':
			if (SrcCfgValuesPrompt(src, g_csrccfg[src])) // returns true if QUIT selected
			{
				bNew = false;
			}
			else
				bNew = true;
			eact = CMD_ACTION_SET;
			bExit = true;
			break;
		case ENTER:
			break;
		default:
			tcout << _T("\nUnsupported Selection\n");
			break;
		}

	} while (!bExit);


	return (bNew | bReset);
}
void SourceConfig(int src)
{
	int r = 0;

	if (r = vpcmd_dev_units(g_ctx, g_hnd, CMD_ACTION_GET, &g_cUnits))
		DisplayError(r, "vpcmd_dev_units CMD_ACTION_GET");

	else if (r = vpcmd_dev_sourcecfg(g_ctx, g_hnd, src, CMD_ACTION_GET, g_csrccfg[src]))
		DisplayError(r, "vpcmd_dev_sourcecfg CMD_ACTION_GET ");

	else
	{
		g_csrccfg[src].Units() = g_cUnits;

		std::string s; s.reserve(200);
		cout << "Source " << src << " Configuration:" << endl;
		g_csrccfg[src].Report(s);
		cout << s.c_str() << endl;
		s.clear();

		eCmdActions eAct = CMD_ACTION_SET;
		if (SrcCfgPrompt(src, eAct))
		{
			std::string s("vpcmd_dev_sourcecfg ");
			s += CVPcmd::actstr(eAct); s += " ";

			r = vpcmd_dev_sourcecfg(g_ctx, g_hnd, src, eAct, g_csrccfg[src]);
			DisplayError(r, s.c_str());
		}
	}
}
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
CSrcRotCfg g_csrcrot;
void ShowSrcRotValuesMenu(eViperOriUnits eU)
{
	tcout << "Source Rotation angles are specified as " << CVPcmd::oriunitsstr(eU) << "." << endl;
	if (eU == ORI_QUATERNION)
		tcout << "Enter 4 comma-separated floating-point values (e.g. 1.0, 0.0, 0.0, 0.0) for Source Rotation Quaternion." << endl;
	else
		tcout << "Enter 3 comma-separated floating-point values (e.g. 0.0, 0.0, 0.0) for Source Rotation Angles." << endl;
	tcout << _T(">> ");
}
bool SrcRotValuesPrompt(CSrcRotCfg & bcfg)
{
	bool bQuit = false;
	bool bExit = false;
	float to_in[4];

	int args_needed = bcfg.Units() == ORI_QUATERNION ? 4 : 3;

	ShowSrcRotValuesMenu(bcfg.Units());
	do
	{
		int argcount = 0;
		TCHAR finput[100];
		while (argcount < args_needed)
		{
			tcin.getline(finput, 100);// , _T(','));
			argcount = _stscanf_s(finput, _T("%f , %f , %f , %f"), &to_in[0], &to_in[1], &to_in[2], &to_in[3]);
		}
		CSrcRotCfg tmp(to_in);
		tmp.Units() = bcfg.Units();
		std::string s; s.reserve(200);
		tmp.Report(s);

		tcout << _T("You entered Source Rotation Angle Values ") << endl << s.c_str() << endl;
		tcout << _T("Press Enter to continue, ESC to quit, C or c to repeat>> \n");
		tcout << _T("ENTER//ESC/Cc>> ");

		int ch;
		ch = _getche();
		if (ch == 0)
			ch = _getche();
		ch = toupper(ch);
		switch (ch)
		{
		case 0:
			tcout << _T("0 input") << endl;
			break;
		default:
			{
				tcout << _T(" Invalid selection\n\n");
				ShowSrcRotValuesMenu(bcfg.Units());
			}
			break;
		case 'C':
			ShowSrcRotValuesMenu(bcfg.Units());
			break;
		case ENTER:
			bExit = true;
			memcpy((SRCROT_CONFIG*)bcfg, (SRCROT_CONFIG*)tmp, sizeof(SRCROT_CONFIG));
			bQuit = false;
			break;
		case ESC:
			cout << "Source Rotation values will not be changed" << endl;
			bQuit = true;
			bExit = true;
			break;
		}

	} while (!bExit);

	return bQuit;
}
void ShowSrcRotMenu()
{
	tcout << _T("\nPlease select an Source Rotation Configuration Option: \n");
	tcout << _T(" R or r: Reset/Clear Source Rotation\n");
	tcout << _T(" N or n: Select Source Rotation angles\n");
	tcout << _T(" ESC:  Exit Source Rotation Configuration Menu\n");
	//tcout << endl;
}
bool SrcRotPrompt(eCmdActions &eact)
{
	bool bNew = false;
	bool bReset = false;
	bool bExit = false;
	bool bBoth = false;

	ShowSrcRotMenu();
	do
	{
		INT ch;

		tcout << _T("\nRr/Nn/ESC>> ");
		ch = _getche();
		if (ch == 0)
			ch = _getche();
		ch = toupper(ch);

		switch (ch)
		{
		case ESC:
			bExit = true;
			break;
		case 'R':
			bReset = true;
			eact = CMD_ACTION_RESET;
			bExit = true;
			break;
		case 'N':
			if (SrcRotValuesPrompt(g_csrcrot)) // returns true if QUIT selected
			{
				bNew = false;
			}
			else
				bNew = true;
			eact = CMD_ACTION_SET;
			bExit = true;
			break;
		case ENTER:
			break;
		default:
			tcout << _T("\nUnsupported Selection\n");
			break;
		}

	} while (!bExit);


	return (bNew | bReset);
}
void SourceRotation(int src)
{
	int r = 0;

	if (r = vpcmd_dev_units(g_ctx, g_hnd, CMD_ACTION_GET, &g_cUnits))
		DisplayError(r, "vpcmd_dev_units CMD_ACTION_GET");

	else if (r = vpcmd_dev_sourcerot(g_ctx, g_hnd, src, CMD_ACTION_GET, g_csrcrot))
		DisplayError(r, "vpcmd_dev_sourcerot CMD_ACTION_GET ");

	else
	{
		std::string s; s.reserve(200);
		cout << "Source " << src << " Source Rotation:" << endl;
		g_csrcrot.Units() = (eViperOriUnits)g_cUnits.ori_units;
		g_csrcrot.Report(s);
		cout << s.c_str() << endl;
		s.clear();

		eCmdActions eAct = CMD_ACTION_SET;
		if (SrcRotPrompt(eAct))
		{
			std::string s("vpcmd_dev_sourcerot ");
			s += CVPcmd::actstr(eAct); s += " ";

			r = vpcmd_dev_sourcerot(g_ctx, g_hnd, src, eAct, g_csrcrot);
			DisplayError(r, s.c_str());
		}
	}
}


