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

        serialexa.c
        Copyright (c) Z-World, 2001

        This program directs all of the data from a serial port to a
        TCP port and vice versa.

        This is configurable at compile time.  Change the MY_IP_ADDRESS,
        MY_NETMASK, MY_GATEWAY, MY_NAMESERVER, TIME_ZONE, and vs_info
        structures to match your application.

        This program was initially built from the state.c example program.

        JJB 04/27/2001: Created
        JJB 06/06/2001: Added a tcp_tick(NULL) to main to insure arp
                        requests are handled while we are waiting for
                        a connection.
        JJB 06/08/2001: vs_state->open_timeout was not initialized.
                        this caused the program not to respond the
                        first time it was compiled.  open_timeout
                        does not apply when dealing with a passive
                        socket.
        
*******************************************************************************/

//#define USE_STDIO
//#define VERBOSE

#define VS_MAXOFFSET 512


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

/*
 *  Set the ip address, netmask.  This example assumbes that you are
 *  on the same subnet as the controller.  If you need to route through
 *  a gateway you can define MY_GATEWAY in a similiar way to the ip or
 *  netmask.
 *  
 *  If you will be viewing pages from a different subnetwork and you
 *  will not be using address translation you may need to set up the
 *  default gateway. exa:
 *
 *  #define MY_GATEWAY "10.10.6.19"
 *
 */

#define MY_IP_ADDRESS   "10.10.6.100"
#define MY_NETMASK      "255.255.255.0"
#define MY_GATEWAY		"10.10.6.1"
#define MY_NAMESERVER	"10.10.6.19"

/*
 *  The TIMEZONE compiler setting gives the number of hours from
 *  local time to Greenwich Mean Time (GMT).  For pacific standard
 *  time this is -8.  Note:  for the time to be correct it must be set
 *  with tm_rd which is documented in the Dynamic C user manual.
 *
 */

#define TIMEZONE        -8

/*
 *  memmap forces the code into xmem.  Since the typical stack is larger
 *  than the root memory, this is commonly a desirable setting.  Another
 *  option is to do #memmap anymem 8096 which will force code to xmem when
 *  the compiler notices that it is generating withing 8096 bytes of the
 *  end.
 *
 *  #use the Dynamic C TCP/IP stack library and the HTTP application library
 *
 */

#define CINBUFSIZE 2047
#define COUTBUFSIZE 2047

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

#memmap xmem
#use "dcrtcp.lib"

/*
 *		Connection States
 *
 */

#define VS_INIT			0		/* closed or never opened */
#define VS_LISTEN			1		/* waiting for a connection */
#define VS_OPENING		2		/* wait while we open a connection */
#define VS_OPEN			3		/* we have a connection */
#define VS_WAITCLOSE		4		/* wait for the connection to be torn down */

/*
 *		VsState contains all of the information necessary
 *		to manage the connection.  The state is one of the above 
 *		connection states.  
 *
 */

#define VS_MODEOFF			0
#define VS_MODEACTIVE		1
#define VS_MODEPASSIVE		2

typedef struct
{
	int state;
	tcp_Socket socket;

	int	offset;
	long	last_character;
	char	buffer[VS_MAXOFFSET];

	int	open_timeout;
} VsState;

/*
 *		VsState holds the configuration information.  The
 *		factory_defaults file is copied to vs_info when
 *		the RESET command is issued.
 *
 */

typedef struct
{
	int	port;					// port to listen on when in VS_MODEPASSIVE
	int	timeout;				// intercharacter delay before flushing characters (ms)
	long	baud;					// serial port baud rates

	int	mode;					// VS_MODEACTIVE, VS_MODEPASSIVE, VS_MODEOFF
	long	dest_ip;				// ip address to call when in VS_MODEACTIVE
	int	dest_port;			// port to call when in VS_MODEACTIVE

	int	binary;				// TCP_MODE_BINARY or TCP_MODE_ASCII

	int	open_timeout;		// interopen delay when unsuccessful open socket (ms)
	
} VsInfo;

VsInfo vs_info;
VsState vs_state;

const VsInfo factory_defaults =
{
	23,
	0,
	57600,
	
	VS_MODEPASSIVE,
	0,
	0,
	
	TCP_MODE_BINARY,

	1000
};

/*
 *		This function sets the VsState.state to the initial
 *		state so it will be initialized on the first pass 
 *		through the handler.
 *
 */

void vs_init(VsState* vs_state)
{
	vs_state->state=VS_INIT;
	vs_state->offset=0;
	vs_state->last_character=MS_TIMER;
	vs_state->open_timeout=vs_info.open_timeout;

	memcpy(&vs_info,&factory_defaults,sizeof(vs_info));
}

/*
 *		This function drives the state machine.  
 *
 *		It can be in 3 main states.  VS_INIT is the initial
 *		state. It will wait for the interopen time if it is
 *		an active connection and then open the connection.
 *		If it is a passive connection, VS_INIT will listen
 *		for a new connection.
 *
 *		The VS_LISTEN and VS_OPENING state wait for a
 *		connection to be established and change the
 *		socket mode when it is.
 *
 *		When the state is VS_OPEN the state machine passes
 *		anything from the serial port to the tcp port and
 *		from the tcp port to the serial port.  Notice the
 *		code monitoring the vs_state.timeout.  When a timeout
 *		is specified in the vs_info structure the VS_OPEN
 *		state will wait until it either sees an inter character
 *		gap in vs_info.timeout ms or until the internal buffer
 *		is full.  This has two effects.  First it is requires
 *		less overhead on the network.  Second it may increase
 *		the burstiness of the data.  Set vs_info.timeout to
 *		zero if you don't want that behavior.
 *
 *		When the state machine notices the socket has been
 *		closed for some reason it returns the state back to
 *		VS_INIT.
 *
 */

void vs_handler(VsState* state)
{
	auto tcp_Socket* socket;
	auto int ch, bytes_written;

	if(vs_info.mode==VS_MODEOFF)
		return;

	socket=&state->socket;
	
	/*
	 *		was the connection reset?
	 */
	if(state->state!=VS_INIT && tcp_tick(socket)==0) {
		state->state=VS_INIT;
		state->open_timeout=vs_info.open_timeout;
	}

	switch(state->state) 
	{
		case VS_INIT:
			/*
			 *		passive open on the socket port
			 */

			if(state->open_timeout && vs_info.mode == VS_MODEACTIVE) {
				costate {
					waitfor(DelayMs(state->open_timeout));
					state->open_timeout=0;
				}

				if(state->open_timeout)
					break;
			}

			serCopen(vs_info.baud);

			if(vs_info.mode == VS_MODEPASSIVE) {
				tcp_listen(socket,vs_info.port,0,0,NULL,0);
				state->state=VS_LISTEN;
#ifdef VERBOSE
				printf("\nListening on socket\n");
#endif			
			} else if(vs_info.mode == VS_MODEACTIVE) {
				tcp_open(socket,0,vs_info.dest_ip,vs_info.dest_port,NULL);
				state->state=VS_OPENING;
#ifdef VERBOSE
				printf("\nOpening socket\n");
#endif			
			} 
			break;
			
		case VS_LISTEN:
		case VS_OPENING:
			/*
			 *		wait for a connection
			 */
			 
			if(sock_established(socket)) {
				state->state=VS_OPEN;
				sock_mode(socket,vs_info.binary);

#ifdef VERBOSE
				printf("New Connection\n");
#endif				
			}
			break;
			
		case VS_OPEN:
			if(vs_info.timeout!=0 &&
				state->offset &&
				(state->last_character+vs_info.timeout)<MS_TIMER) {
				bytes_written=sock_fastwrite(socket,state->buffer,state->offset);

				/*
				 *		Hmmm... We weren't able to write all the bytes out.
				 *		Since we don't want to loose any characters, we will
				 *		shift everything over and hopefully write it soon.
				 *
				 */

				if(bytes_written!=state->offset) {
					memcpy(state->buffer,state->buffer+bytes_written,state->offset-bytes_written);
					state->offset = bytes_written;
					break;
				} else
					state->offset = 0;
			}
			
			/*
			 *		process any characters.
			 */
			 
			while(sock_dataready(socket) && serCwrFree()!=0) {
				ch=sock_getc(socket);
#ifdef USE_STDIO			
				printf("%c",ch);
#endif				
				serCputc(ch);
			}

			/*
			 *		If we aren't worried about interpacket delay
			 *		just send the characters if there is room in
			 *		the buffer.
			 *
			 */
			 
			if(vs_info.timeout==0) {
				while(sock_tbleft(socket)!=0 && (ch=serCgetc())!=-1) {
#ifdef USE_STDIO			
					printf("%c",ch);
#endif				
					sock_putc(socket,ch);
				}
			} else {
				while(state->offset<VS_MAXOFFSET && (ch=serCgetc())!=-1) {
#ifdef USE_STDIO			
					printf("%c",ch);
#endif				
					state->buffer[state->offset++]=ch;
					state->last_character=MS_TIMER;
				}
			}

			/*
			 *		We should immediately flush characters if the buffer
			 *		is full.
			 *
			 */

			if(state->offset==VS_MAXOFFSET) {
				bytes_written=sock_fastwrite(socket,state->buffer,state->offset);

				/*
				 *		Hmmm... We weren't able to write all the bytes out.
				 *		Since we don't want to loose any characters, we will
				 *		shift everything over and hopefully write it soon.
				 *
				 */

				if(bytes_written!=state->offset) {
					memcpy(state->buffer,state->buffer+bytes_written,state->offset-bytes_written);
					state->offset = bytes_written;
				} else
					state->offset = 0;
			}
			
			break;
			
		case VS_WAITCLOSE:
			break;
			
		default:
			/*
			 *		how did we get here?  programming error?
			 */
			 
			state->state=VS_INIT;
			break;
	}
}

/*
 *		main() initializes DCRTCP by calling sock_init and the
 *		VsState by vs_init.  It then calls vs_handler repeatitively
 *		the the VsState.  If you wanted to extend this to have 
 *		multiple connections, add more VsStates with their own calls
 *		to the vs_handler.
 *
 */

main()
{
	sock_init();
	
	vs_init(&vs_state);
	serCopen(vs_info.baud);

   while (1) {
   	tcp_tick(NULL);
		vs_handler(&vs_state);
	}
}
