/***************************************************************************
	dac-ctrl.c
	
   Z-World, 2001
   This sample program is used with the BL20XX series controllers.

   Presents a web page and Java applet to client browser.  The Java applet
   presents a slider to the user.  Moving the slider instantaneously (sic)
   changing the output voltage.
   
   The program presents two control interfaces: HTTP and a backdoor socket.
   The user interface is loaded form the HTTP server this code runs.  To 
   actually control the DAC, a private socket is used.  The ZSERVER would
   be another idea, but since the interface is driven by the Java applet,
   that was more than is needed here.  If using a firewall, you must open
   up the second port DAC_SERVER_PORT so the applet can send commands to
   the BL2000.

   This program outputs a voltage that can be read with a voltmeter. The 
   output voltage is computed with using the calibration constants that
   are read from EEPROM (simulated in Flash).

	!!!This program must be compiled to Flash.
	
   Based on "samples/BL2000/DAC/DaOut1.c"   	

	Instructions:
	Connect a voltage meter to an output channel DAC0 or DAC1.
	Compile and run this program.
	On another computer start up a browser with Java 1.1 support enabled.
	Browse the homepage for this device.  Applet will run, and connect
	to a back channel on separate TCP port.  Change the sliders and 
	watch the output voltage change!

	All replies from the BL2000 either start with "HELP:", "ERR:", "NOTE:"
	or "__AD:" depending on severity of the message.  (The last is an A/D
	update message.)  This allows the application to filter the messages.

	___________________________README___________________________

	INTRODUCTION:
	=============

	Sample Java Applet for use on BL2000.  You run the Applet in a web
	browser on some client, and it communicates back the DAC-CTRL.C
	program running on the BL2000.
	     TESTED:	Dynamic C 7.05P
			Java 1.3.0_02 (downloaded from Sun, April 2001)
			Microsoft Internet Explorer 5.00
			Netscape Navigator 4.72

	Then the Java "class" files are checked in, so a Java compiler
	is not necessary to demonstrate this program.  These files
	were generated using Java compiler version 1.3.0_02 downloaded
	from Sun in April 2001.  Requires listener calsses from Java
	1.1 runtime. Applet now ensures these classes exist before it
	tries to use them!

	COMPILE:
	========

	If you want to re-compile the Java source (not required), use
	the simple MS-DOS batch file "m.bat" in the ./pages/
	directory.  Invoke it with no options to compile the source.
	Invoke as "m doc" to generate Java Docs for all the classes.
	Use "m clean" to remove all "class" files and most files in
	the ./pages/DOCS/ directory.  ("m" is short for "make".)

	Compile and download the C program.  Using an "HttpSpec"
	structure, it includes the Java "class" files along with the
	SHTML file (server-side includes).  Running this program
	initializes both analog output channels.  Only one applet may
	connect at a time.


	DOCUMENTATION:
	==============

	If you want the class documentation, you need to run "javadoc".
	Those files are not part of the sample application, sorry.
	From the batch file type "m doc" to run javadoc.


	RUNNING THE APPLET:
	===================

	The applet is referenced from inside a web page.  Surfing to
	the "home page" of the BL200 will give you the page containing
	the applet script.  If Java execution is enabled in your web
	browser, it will pull down the required "*.class" files.
	Using Server-Side Include (SSI) values, the BL2000 will pass a
	TCP port to connect back to, and a session key to verify the
	sender.  The session key is NOT stored as a browser cookie.

	Also make sure the port DAC_SERVER_PORT is reachable on the
	BL2000.  The HTTP port is only used to load the Java class
	files.  Commands are sent through the server port.

	The applet will present two horizontal pairs of labels and scrollbars.
	Hook up a voltmeter the BL2000 digital-to-analog output ports (there 
	are two of them).  DAC1 is to the left, right next to ground, and DAC0
	is on the right (third from the left-edge of the barrier strip).

	On the browser, slide either slider.  The voltage on that DAC
	(0 for top scrollbar, 1 for bottom scrollbar) will vary
	proportionally.  The BL2000 informs the applet of the maximum
	voltage it will accept.  When it receives back the command to
	set the voltage, it passes that to directly to analogOut().  If
	the C program prints out the voltage as it goes, the update
	time slows down.

	When you are done, either close the browser or leave the page.
	That will close the network connection.



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

#define  SW_WATCH 	0

#define  MAX_CHANNELS	(DACHANNELS)

#define  MAX_AD_CHANNELS   8		/* 0 .. N-1 */

#if (_BOARD_TYPE_ < 0x0800 || _BOARD_TYPE_ > 0x08FF)
#error "DAC-CTRL.C only supports BL20XX series boards."
#endif


/***********************************
 * Configuration                   *
 * -------------                   *
 * All fields in this section must *
 * be altered to match your local  *
 * network settings.               *
 ***********************************/

/*
 * You must change the following values to whatever
 * your local IP address and netmask are. Contact your
 * network administrator for these numbers.
 */
#define MY_IP_ADDRESS   "10.10.6.100"
#define MY_NETMASK      "255.255.255.0"

/*
 *  Local port for DAC controller.  Use by Java Applet.  There is
 *  nothing magical about this port number.  If using a firewall,
 *  you must ALSO open up this port.
 */
#define  DAC_SERVER_PORT	17076

/*
 * Web server configuration
 */

/*
 * Only one server is needed for a reserved port
 */
#define HTTP_MAXSERVERS   1
#define MAX_TCP_SOCKET_BUFFERS 4

/*
 * Our web server as seen from the clients.
 * This should be the address that the clients (netscape/IE)
 * use to access your server. Usually, this is your IP address.
 * If you are behind a firewall, though, it might be a port on
 * the proxy, that will be forwarded to the Rabbit board. The
 * commented out line is an example of such a situation.
 */
#define REDIRECTHOST		MY_IP_ADDRESS
//#define REDIRECTHOST	"proxy.domain.com:1111"


/********************************
 * End of configuration section *
 ********************************/

/*  OVERRIDE: Java makes _long_ filenames. */
#define HTTP_MAXNAME       38

#memmap xmem
#use "BL20xx.LIB"
#use "dcrtcp.lib"

#use "http.lib"

#ximport "samples/BL2000/applet/pages/dac-control.shtml"    index_html
#ximport "samples/BL2000/applet/pages/AnalogApplet.class"	ValueListenerBoardUpdater_class
#ximport "samples/BL2000/applet/pages/RabbitNetworkClient.class"	RabbitNetworkClient_class
#ximport "samples/BL2000/applet/pages/ValueListenerBoardUpdater.class"	ValueListenerUpdater_class
#ximport "samples/BL2000/applet/pages/ValueDisplay.class"		ValueDisplay_class


/*  Have server print debugging messages? (boolean) */
char			dac_debug;

float 		maxAnalogVolts;

/*  Which client we're servicing: */
unsigned 	currSession;

tcp_Socket	serverSocket;
unsigned    serverTcpPort;

char  		serverState;
   #define  SS_UNSET   	   0
   #define  SS_LISTENING   1
   #define  SS_IN_USE	   2
   #define  SS_CLOSING	   3

/*  Store the last value output on the analog DAC. */
float 	analogOut[MAX_CHANNELS];

int		clientWatchingChannel;   		/* <0 not watching, >=0 is channel. */
float 	watchingLastValue;


/* the default for "/" must be first */
const HttpType http_types[] =
{
   { ".shtml", "text/html", shtml_handler}, // ssi
   { ".html", "text/html", NULL},           // html
   { ".cgi", "", NULL},                     // cgi
	{ ".class", "application/octet-stream", NULL},  // Java class file	
   { ".gif", "image/gif", NULL},
   { ".jpg", "image/gif", NULL}
};
/** For more information about Internet media types, please read RFCs 2045,
 *  2046, 2047, 2048, and 2077.  The Internet media type registry is at
 *  <ftp://ftp.iana.org/in-notes/iana/assignments/media-types/>.
 */

/* --------------------------------------------------------------------- */


void
session_reset()
{
	printf( "session_reset()\n" );
	sock_abort( & serverSocket );
	serverState = SS_CLOSING;
}   /* end session_reset() */


/**
 * 	When a new session is established, need to close down the current
 * 	Java client and start fresh for another.  Only one connection is
 * 	allowed at a time.
 *    Return 0 == call me again, 1 == please continue.
 *    There is no way for a Java applet to gaurentee it tells us when it
 *    goes away.  Maybe a "keep alive" option on the socket?  Instead, we
 *    let a new client usurp an existing client.
 */
int session_new( void )
{

	switch( serverState ) {
		case SS_CLOSING :
				if( serverSocket.state == tcp_StateLISTEN ) {
					serverState = SS_LISTENING;
					return 0;
				} else
				if( serverSocket.state != tcp_StateCLOSED ) {
					return 0;
				}
		   	serverState = SS_UNSET;
			   /* .. fall thru .. */

		case SS_UNSET :
				/*  rand() returns "float" !!  Scale to 1..65000 (kinda random). */
				currSession = 1 + (unsigned)(rand() * 65000);
				clientWatchingChannel = -1;
				watchingLastValue = - 1.0;

				tcp_listen( & serverSocket, DAC_SERVER_PORT, 0L, 0,
							    NULL /* no signal handler */, 0 /* wait forever.. */ );
				serverState = SS_LISTENING;
				break;

		case SS_LISTENING :
				/*  We're listening and that is good.  Stop calling here! */
				break;

		default :
				return 0;
	}

   return 1;
}   /* end session_new() */


/**
 *     WATCHAD  -1 | 0 .. 7  
 * 	 	Allows client to watch a particular A/D input channel.  Use "-1"
 *   		to stop monitoring any channels.  When monitoring, we can generate
 *   		messages, up to 2 per second of its current value.
 */
void
cmd_watch_ad( int argc, char * argv[], tcp_Socket * sock )
{
   auto int    channel;

   if( argc != 2 ) {
 USAGE :
   	sock_puts( sock, "ERR: usage: WATCHAD -1 | 0 .. 7" );
   	return ;
   }

   channel = atoi(argv[1]);
	if( channel < 0 ) {
		/*  Values less than 0 mean turn it off. */
		clientWatchingChannel = -1;
#if SW_WATCH
		printf( " watch A/D OFF\n" );
#endif
	} else {
		/* Ensure channel is valid. */
		watchingLastValue = - 1.0;
		if( channel >= MAX_AD_CHANNELS ) {
			goto USAGE ;
		}
		clientWatchingChannel = channel;
#if SW_WATCH
		printf( " now watching A/D #%d\n", channel );
#endif
	}

}   /* end cmd_watch_ad() */


/**
 *     GETDAC  { 1 | 0 }  
 * 	 	Prints a value from 0.0 to 4.0.  Would be nice to somehow sample
 *  		output to report the "current voltage there" but that requires
 *  		extra wiring.
 */
void
cmd_get_dac( int argc, char * argv[], tcp_Socket * sock )
{
   auto char   buffer[ 80 ];
   auto int    channel;

	if( argc != 2 || !isdigit(argv[1][0]) ) {
		sock_puts( sock, "ERR: usage: GET DAC  { 1 | 0 }" );
		return ;
	}

	channel = argv[1][0] - '0';
	if( (channel == 0 || channel == 1) ) {
		sprintf( buffer, "NOTE: DAC #%d = %f", channel, analogOut[ channel ] );
	} else {
		strcpy( buffer, "ERR: channel out of range." );
	}
	sock_puts( sock, buffer );

}   /* end cmd_get_dac() */


/**
 *     SETDAC  { 1 | 0 }  0.0 ... 4.0
 */
void
cmd_set_dac( int argc, char * argv[], tcp_Socket * sock )
{
	auto int 	channel;
	auto float	voltout;

	if( argc != 3 || !isdigit(argv[1][0]) ) {
		sock_puts( sock, "ERR: usage: SET_DAC  { 1 | 0 }  0 ... 4096" );
		return ;
	}

	channel = argv[1][0] - '0';
	voltout = atof( argv[2] );

	if( (channel == 0 || channel == 1) && (0.0 <= voltout && voltout <= 4.0) ) {
		anaOutVolts(channel, voltout);
		analogOut[ channel ] = voltout;
#if SW_WATCH
		printf( " anaOutVolts( %d, %f )\n", channel, voltout );
#endif
	} else {
		sock_puts( sock, "ERR: channel or voltage out-of-range." );
	}

}   /* end cmd_set_dac() */


/**
 *    Handle a command from our Java applet client.  Lines are formatted:
 *            SESSION_ID  CMD  ARGS  CR
 *    where:
 *       SESSION_ID was provided when the Java applet was downloaded.
 *       CMD is "GETDAC", "SETDAC", "EXIT", "QUIT", "HELP".
 *
 * 	Uses strtok(), so this routine really isn't reentrant.
 */
#define  CMD_MAX_ARGS  8

void
do_cmd( tcp_Socket * sock )
{
	auto char *  parts[ CMD_MAX_ARGS+1 ];
   auto char    buffer[ 100 ];
	auto char *  next;
	auto int 	 argc;

	for( argc=0 ; argc < CMD_MAX_ARGS+1 ; ++argc ) {
		parts[argc] = NULL;
	}

	/*  Ask the socket for a line-full.  DCR strtok() returns NULL if a word
	 *  exists with _no_ surrounding "token separation."  By putting a blank
	 *  in first, I hope to force the existance of a token.  The strtok() is
	 *  _not_ ANSI compatible.  :(
	 *  Of course, strok() is not reentrant.
	 */
	buffer[0] = ' ';
	sock_gets( sock, buffer+1, sizeof(buffer)-2 );

	/*  Parse the string.  If empty, just return; hitting ENTER is not a crime. */
	for( argc=0, next = strtok(buffer," \t") ;
		  NULL != next && argc < CMD_MAX_ARGS ;
		  next = strtok(NULL, " \t") ) {
		parts[argc++] = next;
	}
	if( argc == 0 ) {
		return;
	}

	next = parts[0];
	if( 0 == strcmpi(next, "SETDAC") ) {
		cmd_set_dac( argc, parts, sock );
	} else
	if( 0 == strcmpi(next, "GETDAC") ) {
		cmd_get_dac( argc, parts, sock );
	} else
	if( 0 == strcmpi(next, "WATCHAD") ) {
		cmd_watch_ad( argc, parts, sock );
	} else
	if( 0 == strcmpi(next, "EXIT") || 0 == strcmpi(next, "QUIT") ) {
		session_reset();
	} else
	if( 0 == strcmpi(next, "PING") ) {
		sock_puts( & serverSocket, "OK" );
	} else
	if( 0 == strcmpi(next, "HELP") || 0 == strcmp(next, "?") ) {
		sock_puts( & serverSocket, "HELP: Valid cmds: " \
						"\"GETDAC\", \"SETDAC\", \"WATCHAD\", " \
						"\"EXIT\", \"QUIT\", \"PING\", \"HELP\" ");
	} else {
	   /*  Want to format an error message.  However the bad command is stored
	    *  in the tcp "input buffer".  Create an error message one part at a time.
	    */
		sock_write( sock, "ERR: bad cmd <", 14 );
		sock_write( sock, next, strlen(next) );
		sock_puts ( sock, ">" ); 
	}

}   /* end do_cmd() */


void
serverAnnounce( tcp_Socket * sock )
{
   auto char   buffer[40];
   
	printf( " ** established **\n" );

   sprintf( buffer, "NOTE: BL20%d0 ready.", _BOARD_TYPE_ & 0x0007 );
   sock_puts( sock, buffer );
}  /* end serverAnnounce() */


/* --------------------------------------------------------------------- */


/**
 * 	When a client is watching the A/D channel, provide unsolicitated
 * 	setting updates.
 */
void
watchingAD( tcp_Socket * sock )
{
   auto char    buffer[64];
	auto float   newValue;

	newValue = anaInVolts( clientWatchingChannel );
	if( newValue != watchingLastValue ) {
		watchingLastValue = newValue;
		sprintf( buffer, "__AD: %d %f", clientWatchingChannel, newValue );
		sock_flushnext( sock );
		sock_puts( sock, buffer );
	}

}   /* end watchingAD() */


/* --------------------------------------------------------------------- */

/**
 *    Each client is given a unique "cookie" to return on each update.
 *    A session ID of 0 means we're busy.
 * 	RETURN:  1 == we're done being called, answer is complete.
 */
int
make_session_ident(HttpState* state)
{

	session_new();
	if( serverState != SS_LISTENING ) {
		strcpy( state->buffer, "0" );
	} else {
		sprintf( state->buffer, "%u", currSession );
	}

   state->headeroff = 0;
   state->headerlen = strlen(state->buffer);

#if SW_WATCH
	printf( "new session ID is %u\n", currSession );
#endif

	return 1;
}   /* end make_session_ident() */


/* --------------------------------------------------------------------- */

const HttpSpec http_flashspec[] = 
{
   { HTTPSPEC_FILE, 	"/",		index_html,    NULL, 0, NULL, NULL},
   { HTTPSPEC_FILE, 	"/index.html",	index_html,    NULL, 0, NULL, NULL},

   { HTTPSPEC_FILE, 	"/AnalogApplet.class",	ValueListenerBoardUpdater_class,    NULL, 0, NULL, NULL},
   { HTTPSPEC_FILE, 	"/ValueDisplay.class",		ValueDisplay_class,    NULL, 0, NULL, NULL},
   { HTTPSPEC_FILE, 	"/RabbitNetworkClient.class",	RabbitNetworkClient_class,    NULL, 0, NULL, NULL},
   { HTTPSPEC_FILE, 	"/ValueListenerBoardUpdater.class",	ValueListenerUpdater_class,    NULL, 0, NULL, NULL},

   { HTTPSPEC_VARIABLE, "CMD_PORT",	0,    & serverTcpPort, INT16, "%u", NULL},
   { HTTPSPEC_VARIABLE, "MAX_VOLTAGE",	0,  & maxAnalogVolts, FLOAT32, "%f", NULL},
   { HTTPSPEC_FUNCTION,	"SESSION_ID",	 0,  make_session_ident, 0, NULL, NULL},
};


main()
{
   int  j;

	brdInit();     		/* Put the BL2000 DAC in order. */

	dac_debug = FALSE;
	serverState = SS_UNSET;
	serverTcpPort = DAC_SERVER_PORT;
	clientWatchingChannel = -1;
	maxAnalogVolts = 4.0;
	{
		/* int j; */ for(j=0 ; j < MAX_CHANNELS ; ++j ) { analogOut[j] = 0.0; }
		anaOutVolts( 0, 1.7 );
		anaOutVolts( 1, 3.141 );
	}
	
	sock_init();
	http_init();
	tcp_reserveport(80);
	session_new();

	for(;;) {

		costate {
		tcp_tick(NULL);
		http_handler();

		/**  handle the TCP service port.  **/
		switch( serverState ) {
			case SS_LISTENING :
					if( sock_established( & serverSocket ) ) {
						sock_mode( & serverSocket, TCP_MODE_ASCII );
						serverState = SS_IN_USE;
						serverAnnounce( & serverSocket );
					// psocket( (sock_type *) &http_servers[x].s );
					}
					break;

			case SS_IN_USE :
					if( sock_dataready( & serverSocket ) ) {
						do_cmd( & serverSocket );
					} else
					if( ! sock_established( & serverSocket ) ) {
						/* Ugh, our client disappeared, reset. */
						printf( " ** lost client **\n" );
						session_reset();
					}
					break;
		}
		}   /* end costate.. */

		costate {
			/*  If client watching an A/D converter, see if it changed since
			 *   the last time around.  If so, send a notify.
			 */
			waitfor( DelayMs(500L) );
			if( clientWatchingChannel >= 0 ) {
				watchingAD( & serverSocket );
			}
		}

	}		
	/*NOTREACHED*/	
}   /* end main() */
