/*
** KeyMacro.c - Sample program for BL1700 (part of the PK2600) to 
** demonstrate some simple uses of the OP7100GE Graphics Engine's 
** built-in keyboard macro.
**
** This program can be modified to run on any Z-World controller 
** with AASCZ0.LIB or AASCZ1.LIB library support by defining the 
** appropriate AASC_DEVICE below.
*/

// define the appropriate AASC serial device type
// Note: The PK2600's built-in BL1700 uses DEV_Z1.
//#define AASC_DEVICE DEV_Z0
#define AASC_DEVICE DEV_Z1

// un-comment the following line(s) to get debug info
//#define GE_DEBUG_GET
//#define GE_DEBUG_PUT
//#define GE_DEBUG_TIME

// un-comment the following line to get right-to-left keypad text direction
//#define RIGHT_TO_LEFT_TEXT

#use aasc.lib
#use aascz0.lib
#use aascz1.lib
#use string.lib
#use vdriver.lib

// OP7100GE.C definitions
#define LANDSCAPE					// undefine for portrait
#define BUFFSIZE					255	// buffer has room for 1 packets
#define BAUDMULT					8		// multiples of 1200 baud

#define MAX_VKSTRING				24		// longest allowable string (used in vk)
#define MASK						255
#define PLAY_QUE_SIZE			100	// number of command pointers in the display que
#define FILLED						0xFF	// if shape is filled

// command buffer offsets
#define ADDR						0
#define LEN							1
#define CMD							2
#define FXN							3
#define SEQ							4
#define DATA0						5

// command buffer values
#define ADDR_SLAVE				0x01

// response buffer values
#define ADDR_MASTER				0x00
#define ACK							0x06
#define NAK							0x15

// local definitions
#define BACKLIGHT_TIME			(10 * 60)
#define BUFSIZE_CMD				255
#define BUFSIZE_RXTX				600

char cmdBuff[BUFSIZE_CMD];
char *promptString = "String?";
char rspBuff[BUFSIZE_CMD];
char rxBuff[BUFSIZE_RXTX];
char txBuff[BUFSIZE_RXTX];
CHANNEL chan;

long CmdBackLight(char *cmd, char *rsp, int onOff);
long CmdClearScreen(char *cmd, char *rsp);
long CmdDelayPlay(char *cmd, char *rsp, long delay);
long CmdKeyboardMacro(char *cmd, char *rsp, char *string, int onOff, int flags, void *max, void *min);
long CmdPutTextXY(char *cmd, char *rsp, char *string, int x, int y);
long CmdSetFont(char *cmd, char *rsp, int fontID);
long CmdSetString(char *cmd, char *rsp, char *string);
long CmdSetTextDir(char *cmd, char *rsp, int textDir);
long CmdStopMacroLoop(char *cmd, char *rsp);
long CmdSuperReset(char *cmd, char *rsp);
long GetResponse(long delayMS, char *rsp);
long GetTimedChar(unsigned long delayMS, char *rspChar);
long PutCmdGetRsp(unsigned long delayMS, char *cmd, unsigned crcLen, char *rsp);
long PutCommand(long delayMS, char *cmd, int crcLen);

xmem
main()
{
	auto char echoLine[BUFFSIZE - DATA0 - 2];
	auto char promptLine[MAX_VKSTRING];
	auto int backLight;
	auto int crc, crcLen;
	auto int keyFlags;
	auto unsigned long backLightTimer;
	auto long maxL, minL, returnL;
	auto float maxF, minF, returnF;
	
	VdInit();	// init virtual driver, call _GLOBAL_INIT()
	if((chan = aascOpen(AASC_DEVICE, 0, ((ASCI_PARAM_1200 * BAUDMULT) | 
			ASCI_PARAM_8N1), NULL)) == NULL)
	{
		exit(-1);
	}
	else
	{
		// set up the CHANNEL's circular buffers
		aascSetReadBuf(chan, rxBuff, sizeof(rxBuff));
		aascSetWriteBuf(chan, txBuff, sizeof(txBuff));
		
		aascFlush(chan);
		
		// enable CHANNEL's RS-232 receiver and transmitter
		aascTxSwitch(chan, 1);
		aascRxSwitch(chan, 1);
		
		// set intial float/long maxima/minima
		maxF = maxL = 999;
		minF = minL = -99;
		
		// wait 2 seconds while OP7100GE.C initializes
		tdelay(2000);
		
		// ensure the backlight is on
		backLightTimer = SEC_TIMER + (unsigned long) BACKLIGHT_TIME;
		backLight = 1;
		CmdBackLight(cmdBuff, rspBuff, backLight);
		
		// clear the screen
		CmdClearScreen(cmdBuff, rspBuff);
		
#ifndef RIGHT_TO_LEFT_TEXT						
		// set left-to-right text direction
		CmdSetTextDir(cmdBuff, rspBuff, 0);
#else	
		// set right-to-left text direction
		CmdSetTextDir(cmdBuff, rspBuff, 1);
#endif
		
		// start the virtual keyboard
		keyFlags = 0x03;	// flags to return string
		strcpy(promptLine, promptString);
		CmdKeyboardMacro(cmdBuff, rspBuff, promptLine, 1, keyFlags, 
				(void *) &maxL, (void *) &minL);
		
		while(1)
		{
			if(backLight && (SEC_TIMER > backLightTimer))
			{
				// switch off LCD back light
				backLight = !backLight;
				CmdBackLight(cmdBuff, rspBuff, backLight);
			}
			if(aascReadBufLeft(chan) > 0)
			{
				if(GetResponse(3000L, rspBuff) >= 0)
				{
					if(rspBuff[CMD] == 14)	// keyboard macro sends string response
					{
						// save the response string
						strcpy(echoLine, &rspBuff[DATA0]);
						
						// extend or restart the back light timer
						backLightTimer = SEC_TIMER + (unsigned long) BACKLIGHT_TIME;
						
						if(!backLight)
						{
							// switch on LCD back light
							backLight = !backLight;
							CmdBackLight(cmdBuff, rspBuff, backLight);
						}
						
						// set left-to-right text direction
						CmdSetTextDir(cmdBuff, rspBuff, 0);
						
						// clear OP7100GE.C's keyboard buffer string
						CmdSetString(cmdBuff, rspBuff, "");
						
						CmdSetFont(cmdBuff, rspBuff, 1);	// small default font
						CmdPutTextXY(cmdBuff, rspBuff, 
								"Previous information is displayed for 2 seconds.", 1, 
								(int) ((35 + 16 + 8) * 2));
						
						// insert 2 second delay in macro play queue
						CmdDelayPlay(cmdBuff, rspBuff, 2000L);
						
						// Delay locally to avoid problems with Graphics Engine
						// commands such as SetFont, which are not queued but are
						// instead processed immediately.
						tdelay(2100L);
						
						// clear the screen
						CmdClearScreen(cmdBuff, rspBuff);
						
						// report what the user already knows!
						CmdSetFont(cmdBuff, rspBuff, 3);	// large default font
						CmdPutTextXY(cmdBuff, rspBuff, "Keyboard response:", 1, 1);
						CmdSetFont(cmdBuff, rspBuff, 2);	// medium default font
						CmdPutTextXY(cmdBuff, rspBuff, echoLine, 1, (int) (35 * 1.5));
						CmdSetFont(cmdBuff, rspBuff, 1);	// small default font
						CmdPutTextXY(cmdBuff, rspBuff, 
								"Next entry will be requested within 5 seconds.", 1, 
								(int) ((35 + 16) * 1.5));
						
						// insert 5 second delay in macro play queue
						CmdDelayPlay(cmdBuff, rspBuff, 5000L);
						
						// Delay locally to avoid problems with Graphics Engine
						// commands such as SetFont, which are not queued but are
						// instead processed immediately.
						tdelay(5100L);
						
#ifndef RIGHT_TO_LEFT_TEXT						
						// set left-to-right text direction
						CmdSetTextDir(cmdBuff, rspBuff, 0);
#else					
						// set right-to-left text direction
						CmdSetTextDir(cmdBuff, rspBuff, 1);
#endif				
						
						switch(keyFlags)
						{
						case 0x03:
							// re-start the virtual keyboard in a new mode
							keyFlags = 0x07;	// flags to return password mode string
							strcpy(promptLine, "Password?");
							CmdKeyboardMacro(cmdBuff, rspBuff, promptLine, 1, keyFlags,
									(void *) &maxL, (void *) &minL);
							break;
						case 0x07:
							// re-start the virtual keyboard in a new mode
							keyFlags = 0xCB;	// flags to return limited long integer
							sprintf(promptLine, "Long? [%ld,%ld]", minL, maxL);
							CmdKeyboardMacro(cmdBuff, rspBuff, promptLine, 1, keyFlags,
									(void *) &maxL, (void *) &minL);
							break;
						case 0xCB:
							// set new maximum long limit from keyboard response
							maxL = (maxL == minL) ? 999 : atol(echoLine);
							// re-start the virtual keyboard in a new mode
							keyFlags = 0xD3;	// flags to return limited float
							sprintf(promptLine, "Float? [%.1f,%.1f]", minF, maxF);
							CmdKeyboardMacro(cmdBuff, rspBuff, promptLine, 1, keyFlags,
									(void *) &maxF, (void *) &minF);
							break;
						case 0xD3:
							// set new minimum float limit from keyboard response
							minF = (maxF == minF) ? -99.0 : atof(echoLine);
						default:
							// re-start the virtual keyboard in a new mode
							keyFlags = 0x03;	// flags to return string
							strcpy(promptLine, promptString);
							CmdKeyboardMacro(cmdBuff, rspBuff, promptLine, 1, keyFlags,
									(void *) &maxF, (void *) &minF);
							break;
						}
					}
				}
			}
		}
	}
}

xmem long CmdBackLight(char *cmd, char *rsp, int onOff)
{
//	cmd[ADDR] = (char) ADDR_SLAVE;	// start of header
//	cmd[LEN] = (char) 5;					// remaining length of command
	cmd[CMD] = (char) 70;				// CMD command code (clear screen)
	cmd[FXN] = (char) onOff;			// function code (none)
	cmd[SEQ] = (char) 0;					// sequence (flags)
	return(PutCmdGetRsp(2000L, cmd, 5, rsp));
}

xmem long CmdClearScreen(char *cmd, char *rsp)
{
//	cmd[ADDR] = (char) ADDR_SLAVE;	// start of header
//	cmd[LEN] = (char) 5;					// remaining length of command
	cmd[CMD] = (char) 40;				// CMD command code (clear screen)
	cmd[FXN] = (char) 0;					// function code (none)
	cmd[SEQ] = (char) 0;					// sequence (flags)
	return(PutCmdGetRsp(2000L, cmd, 5, rsp));
}

xmem long CmdDelayPlay(char *cmd, char *rsp, long delay)
{
//	cmd[ADDR] = (char) ADDR_SLAVE;	// start of header
//	cmd[LEN] = (char) 9;					// remaining length of command
	cmd[CMD] = (char) 105;				// CMD command code (delay play)
	cmd[FXN] = (char) 0;					// function code (none)
	cmd[SEQ] = (char) 0;					// sequence (flags)
	* (long *) &cmd[DATA0] = delay;	// data 0-3 (long, delay in mS)
	return(PutCmdGetRsp(2000L, cmd, 9, rsp));
}

xmem long CmdKeyboardMacro(char *cmd, char *rsp, char *string, int onOff, int flags, void *max, void *min)
{
	auto int len;
	
	len = strlen(string);
	len = (len >= MAX_VKSTRING) ? (MAX_VKSTRING - 1) : len;
	
//	cmd[ADDR] = (char) ADDR_SLAVE;	// start of header
//	cmd[LEN] = (char) ?;					// remaining length of command
	cmd[CMD] = (char) 89;				// command code (keyboard macro)
	if(onOff)
	{
		cmd[FXN] = (char) 1;				// function code (play)
		cmd[SEQ] = (char) flags;		// sequence (flags)
		cmd[DATA0] = (char) 0;			// data 0 (unused)
		memcpy((void *) &cmd[DATA0 + 1], min, 4);	// data 1-4 (low limit float/long)
		memcpy((void *) &cmd[DATA0 + 5], max, 4);	// data 5-8 (high limit float/long)
		strncpy(&cmd[DATA0 + 9], string, len);
		cmd[DATA0 + 9 + len] = '\0';	// enforce ASCIIZ terminator
		len += (DATA0 + 11);
	}
	else
	{
		cmd[FXN] = (char) 0;				// function code (remove)
		cmd[SEQ] = (char) 0x00;			// sequence (unused)
		len = 5;
	}
	return(PutCmdGetRsp(6000L, cmd, len, rsp));
}

xmem long CmdPutTextXY(char *cmd, char *rsp, char *string, int x, int y)
{
//	cmd[ADDR] = (char) ADDR_SLAVE;	// start of header
//	cmd[LEN] = (char) ?;					// remaining length of command
	cmd[CMD] = (char) 63;				// CMD command code (put text at x,y)
	cmd[FXN] = (char) 0;					// function code (none)
	cmd[SEQ] = (char) 0;					// sequence (flags)
	* (int *) &cmd[DATA0] = x;			// data 0-1 (x coordinate, left)
	* (int *) &cmd[DATA0 + 2] = y;	// data 2-3 (y coordinate, top)
	strcpy(&cmd[DATA0 + 4], string);
	return(PutCmdGetRsp(5000L, cmd, (DATA0 + 5 + strlen(string)), rsp));
}

xmem long CmdSetFont(char *cmd, char *rsp, int fontID)
{
//	cmd[ADDR] = (char) ADDR_SLAVE;	// start of header
//	cmd[LEN] = (char) 5;					// remaining length of command
	cmd[CMD] = (char) 60;				// CMD command code (set font)
	cmd[FXN] = (char) 0;					// function code (unused)
	cmd[SEQ] = (char) fontID;			// sequence (font index)
	return(PutCmdGetRsp(2000L, cmd, 5, rsp));
}

xmem long CmdSetString(char *cmd, char *rsp, char *string)
{
//	cmd[ADDR] = (char) ADDR_SLAVE;	// start of header
//	cmd[LEN] = (char) ?;					// remaining length of command
	cmd[CMD] = (char) 84;				// CMD command code (set string)
	cmd[FXN] = (char) 0;					// function code (none)
	cmd[SEQ] = (char) 0;					// sequence (flags)
	strcpy(&cmd[DATA0], string);
	return(PutCmdGetRsp(3000L, cmd, (DATA0 + 1 + strlen(string)), rsp));
}

xmem long CmdSetTextDir(char *cmd, char *rsp, int textDir)
{
//	cmd[ADDR] = (char) ADDR_SLAVE;	// start of header
//	cmd[LEN] = (char) 5;					// remaining length of command
	cmd[CMD] = (char) 64;				// CMD command code (set text direction)
	cmd[FXN] = (char) textDir;			// function code (direction)
	cmd[SEQ] = (char) 0;					// sequence (unused)
	return(PutCmdGetRsp(2000L, cmd, 5, rsp));
}

xmem long CmdSuperReset(char *cmd, char *rsp)
{
//	cmd[ADDR] = (char) ADDR_SLAVE;	// start of header
//	cmd[LEN] = (char) 5;					// remaining length of command
	cmd[CMD] = (char) 26;				// command code (super reset)
	cmd[FXN] = (char) 0;					// function code (none)
	cmd[SEQ] = (char) 0;					// sequence (flags)
	return(PutCmdGetRsp(10000L, cmd, 5, rsp));
}

xmem long CmdStopMacroLoop(char *cmd, char *rsp)
{
//	cmd[ADDR] = (char) ADDR_SLAVE;	// start of header
//	cmd[LEN] = (char) 5;					// remaining length of command
	cmd[CMD] = (char) 82;				// command code (stop macro loop)
	cmd[FXN] = (char) 0;					// function code (none)
	cmd[SEQ] = (char) 0;					// sequence (flags)
	return(PutCmdGetRsp(3000L, cmd, 5, rsp));
}

xmem long GetResponse(long delayMS, char *rsp)
{
	auto int index;
	auto unsigned long markerMS;
	
	index = ADDR;
	markerMS = MS_TIMER + delayMS;
#ifdef GE_DEBUG_GET
printf("GetResponse():\n");
#endif
	while((delayMS = GetTimedChar(delayMS, &rsp[index])) >= 0)
	{
#ifdef GE_DEBUG_GET
printf(" 0x%02x", rsp[index]);
#endif
		if(index == ADDR)
		{
			if(rsp[index] == ADDR_MASTER)
			{
				index++;	// found our ADDRess, now look for length
			}
		}
		else if(index == LEN)
		{
			if((rsp[index] >= 5) && (rsp[index] <= 253))
			{
				index++;	// found a reasonable length, now get rest of packet
			}
			else
			{
				if(rsp[index] != ADDR_MASTER)
				{
					index = ADDR;	// reset index, looking for new ADDRess
				}
			}
		}
		else // if(index >= CMD)
		{
			if(++index >= (rsp[LEN] + 2))
			{
				if(((rsp[index - 2] * 256) + rsp[index - 1]) == getcrc(rsp, rsp[LEN], 0))
				{
					// response packet's CRC is correct
#ifdef GE_DEBUG_GET
printf("\n");
#endif
#ifdef GE_DEBUG_TIME
printf("GetResponse() returns %ld mS remaining.\n", (markerMS - MS_TIMER));
#endif
					return(markerMS - MS_TIMER);
				}
				else
				{
					// response packet's CRC is wrong
#ifdef GE_DEBUG_GET
printf("\n");
#endif
#ifdef GE_DEBUG_TIME
printf("GetResponse() returns -2 (CRC error).\n");
#endif
					return(-2L);
				}
			}
		}
	}
#ifdef GE_DEBUG_GET
printf("\n");
#endif
#ifdef GE_DEBUG_TIME
printf("GetResponse() returns -1 (time out error).\n");
#endif
	return(-1L);
}

xmem long GetTimedChar(unsigned long delayMS, char *rspChar)
{
	auto unsigned long markerMS;
	
	markerMS = MS_TIMER + delayMS;
	while(MS_TIMER <= markerMS)
	{
		if(aascReadChar(chan, rspChar) == 1)
		{
			return(markerMS - MS_TIMER);
		}
	}
	return(-1L);
}

xmem long PutCmdGetRsp(unsigned long delayMS, char *cmd, unsigned crcLen, char *rsp)
{
	auto unsigned long markerMS;
	
	markerMS = MS_TIMER + delayMS;
	if((delayMS = PutCommand(delayMS, cmd, crcLen)) < 0)
	{
		return(-1L);
	}
	else if((delayMS = GetResponse(delayMS, rsp)) < 0)
	{
		return(-2L);
	}
	else
	{
		return(markerMS - MS_TIMER);
	}
}

xmem long PutCommand(long delayMS, char *cmd, int crcLen)
{
	auto int cmdLen;
	auto int crc;
	auto unsigned long markerMS;
	
	markerMS = MS_TIMER + delayMS;
	cmdLen = crcLen + 2;
	cmd[ADDR] = ADDR_SLAVE;
	cmd[LEN] = (char) crcLen;
	crc = getcrc(cmd, (char) crcLen, 0);
	cmd[crcLen] = (char) ((crc >> 8) & 0x00FF);
	cmd[crcLen + 1] = (char) (crc & 0x00FF);
	while((aascWriteBlk(chan, cmd, cmdLen, 1) != cmdLen) && (MS_TIMER <= markerMS));
#ifdef GE_DEBUG_PUT
printf("PutCommand():\n");
for(crc = 0; crc < cmdLen; crc++)
{
	printf(" 0x%02x", (int) cmd[crc]);
}
printf("\n");
#endif
#ifdef GE_DEBUG_TIME
printf("PutCommand() returns %ld mS remaining (-1 is time out error).\n", (markerMS - MS_TIMER));
#endif
	return(markerMS - MS_TIMER);
}
