Remove CallbackID

This page documents changes to remove the dependency on HTTPPrint callbackid offsets (&  DynRcrd.bin / FileRcrd.bin when using MDD for web page storage) for enabling dynamic variables in web pages served via the Microchip Application Library TCPIP Stack HTTP2 server

It assumes some knowledge of the TCPIP stack internals and the mechanism Microchip have implemented to enable dynamic variables.  Some additional notes on this topic are available here

MPFS2 files will still process as before including processing of dynamic variables.  Existing CustomHTTPApp HTTP_xxx functions will require no changes.

This has been tested and verified with V5.31 of the MAL TCPIP Stack

Approach

From HTTP2.c HTTPExecuteGet comments:

	This function may service multiple HTTP requests simultaneously.
	Exercise caution when using global or static variables inside this
	routine.  Use curHTTP.callbackPos or curHTTP.data for storage associated
	with individual requests.
  • Thus we need to manage our current call back within curHTTP , so we add any additional variables required to structure HTTP_CONN
  • We could also remove the now unused callback variables (nextCallback & callbackID) from HTTP_CONN structure but to reduce rework for the next TCPIP stack release we won’t (is ), also callbackID is still used as a timer

HTTP2.c HTTPSendFile  intercepts call backs by offset from the current files associated MPFS2 index file

  • We can amend this to ignore the MPFS2 index file and instead we will check each served character for ‘~’

HTTP2.c HTTPSendFile function uses curHTTP.nextCallBack to determine when to stop and change state to SM_HTTP_SEND_FROM_CALLBACK.  It also populates nextCallback for any following dynamic variable

  • We will change HTTPSendFile function to ignore the MPFS2 index file and instead match each (about to be served) character against  ‘~’.  If found, we will   parse out and populate the current dynamic variable name and parameter before setting the state to SM_HTTP_SEND_FROM_CALLBACK

HTTPPrint.h HTTPPrint function is called when the current HTTP state is  SM_HTTP_SEND_FROM_CALLBACK.  It uses the current dynamic variable offset populated per HTTPSendFile function to determine the associated HTTPPrint_xxx function to call

  • We can replace HTTPPrint.h & HTTPPrint function so it is not used (so a rerun of Microchip utility MPFS2.exe won’t cause us any issues) with a new HTTPDynVar.h, HTTPDynVar.c & HTTPDynVar function
  • HTTPDynVar function will use character matching to determine the associated HTTPPrint_xxx function to call (replacing the default offset method)

From HTTP2.h:

	typedef enum
	{
		HTTP_TXT = 0u,		// File is a text document
		HTTP_HTM,			// File is HTML (extension .htm)
		HTTP_HTML,			// File is HTML (extension .html)
		HTTP_CGI,			// File is HTML (extension .cgi)
		HTTP_XML,			// File is XML (extension .xml)
		HTTP_CSS,			// File is stylesheet (extension .css)
		HTTP_GIF,			// File is GIF image (extension .gif)
		HTTP_PNG,			// File is PNG image (extension .png)
		HTTP_JPG,			// File is JPG image (extension .jpg)
		HTTP_JAVA,			// File is java (extension .java)
		HTTP_WAV,			// File is audio (extension .wav)
		HTTP_UNKNOWN		// File type is unknown
	} HTTP_FILE_TYPE;
  • Only some file types are allowed to have dynamic variables (as ‘~’ could legitimately appear in a graphic for example)
  • The above typedef has conveniently ordered the first 5 entries as those HTTPSendFilefunction will look in for dynamic variables
  • A check on curHTTP.fileType <=4 is sufficient to determine if we need to scan for dynamic variables in the current file BUT only if not zipped (MPFS2_FLAG_ISZIPPED)
  • cache-control is driven by whether a file has dynamic variables per curHTTP.nextCallback != 0xffffffff so need to change this to be whether a file may have dynamic variables per curHTTP.fileType <=4 or similar

Changes

HTTP2.h:

Into ‘Server Configuration Settings’ section add our dynamic variable name & parameter maximum lengths:

	#if !defined(DYN_VAR_NAME_LEN)
        #define DYN_VAR_NAME_LEN	(31u)	// PNP Max length is this -1
    #endif
	#if !defined(DYN_VAR_PARAM_LEN)
        #define DYN_VAR_PARAM_LEN	(21u)	// PNP Max length is this -1
    #endif

In ‘HTTP State Definitions’ section add dynVarName and dynVarParam to structure HTTP_CONN

	// Stores extended state data for each connection
	typedef struct
	{
	    DWORD byteCount;                        // How many bytes have been read so far
	    DWORD nextCallback;                     // Byte index of the next callback
	    DWORD callbackID;                       // Callback ID to execute, also used as watchdog timer
	    DWORD callbackPos;                      // Callback position indicator
	    BYTE *ptrData;                          // Points to first free byte in data
	    BYTE *ptrRead;                          // Points to current read location
	    MPFS_HANDLE file;                       // File pointer for the file being served
	    MPFS_HANDLE offsets;                    // File pointer for any offset info being used
	    BYTE hasArgs;                           // True if there were get or cookie arguments
	    BYTE isAuthorized;                      // 0x00-0x79 on fail, 0x80-0xff on pass
	    HTTP_STATUS httpStatus;                 // Request method/status
	    HTTP_FILE_TYPE fileType;                // File type to return with Content-Type
	    BYTE data[HTTP_MAX_DATA_LEN];           // General purpose data buffer
	    BYTE dynVarName[DYN_VAR_MAX_NAME_LEN];      // PNP NEW
	    BYTE dynVarParam[DYN_VAR_MAX_PARAM_LEN];    // PNP NEW
	    #if defined(HTTP_USE_POST)
	    BYTE smPost;                            // POST state machine variable
	    #endif
	} HTTP_CONN;

Amend HTTPIncFile prototype from:

void HTTPIncFile(ROM BYTE* cFile);

to

void HTTPIncFile(BYTE* cFile);

HTTP2.c

Add an include to HTTPDynVar.h (created below)  to replace HTTPPrint.h by changing:

#include "HTTPPrint.h"

to

#include "HTTPDynVar.h"

Amend HTTPProcess cache-control in state SM_HTTP_SERVE_HEADERS from

			if(curHTTP.httpStatus == HTTP_POST || curHTTP.nextCallback != 0xffffffff)

to:

			if(curHTTP.httpStatus == HTTP_POST || curHTTP.fileType <= 4)

Replace HTTPProcess call to HTTPPrint with a call to HTTPDynVar in state SM_HTTP_SEND_FROM_CALLBACK

			HTTPPrint(curHTTP.callbackID);

from

			HTTPDynVar(curHTTP.dynVarName, curHTTP.dynVarParam);

Replace HTTPSend function with:

static BOOL HTTPSendFile(void)

#ifndef MAXDATALENGTH
#define MAXDATALENGTH 64
#endif

{
	WORD numBytes, len;
	BYTE c, data[MAXDATALENGTH];							// PNP
	BYTE dynVarFound;										// PNP

	// Fill data until we either hit PutLen or find a dynamic variable indicator '~'
	numBytes = TCPIsPutReady(sktHTTP);
	dynVarFound = FALSE;

	// Get/put as many bytes as possible in MAXDATALENGTH chunks
	while(numBytes > 0u)
	{
		len = 0;
		while(len < MAXDATALENGTH && numBytes > 0u)
		{
			if(MPFSGet(curHTTP.file, &c))
			{
				curHTTP.byteCount++;
				if(curHTTP.fileType <=  4 && c == '~' && !(MPFSGetFlags(curHTTP.file) & MPFS2_FLAG_ISZIPPED))
				{	// Start of Dynamic variable
					dynVarFound = TRUE;
					break;
				}
				else
				{	// Valid data
					numBytes--;
					data[len++] = c;
				}
			}
			else
			{
				TCPPutArray(sktHTTP, data, len);
				return TRUE;
			}
		}
		TCPPutArray(sktHTTP, data, len);
		if(dynVarFound)
			break;
	}
	if(dynVarFound)
	{
		// Read in Dynamic Variable name
		// From first ~ until we find either '~', ':' or '('
		len = 0;
		memset(curHTTP.dynVarName,'\0',sizeof(curHTTP.dynVarName));
		while (MPFSGet(curHTTP.file, &c))
		{
			curHTTP.byteCount++;
			if(c != '~' && c != ':' && c != '(' )
			{
				if(len < DYN_VAR_MAX_NAME_LEN)		// Ignore any extra characters over our allowed length
					curHTTP.dynVarName[len] = c;
				len ++;
			}
			else
				break;
		}

		if(c != '~')
		{
			// Read in Dynamic Variable parameter until we find either a ')' or '~'
			len = 0;
			memset(curHTTP.dynVarParam,'\0',sizeof(curHTTP.dynVarParam));
			while (MPFSGet(curHTTP.file, &c))
			{
				curHTTP.byteCount++;
				if(c != '~' && c != ')')
				{
					if(len < DYN_VAR_MAX_PARAM_LEN)		// Ignore any extra characters over our allowed length
						curHTTP.dynVarParam[len] = c;
					len ++;
				}
				else
					break;
			}
		}

		// If we didnt end on '~' look for it (might have been a ')'
		if(c != '~')
			while (MPFSGet(curHTTP.file, &c))
			{
				curHTTP.byteCount++;
				if(c == '~')
					break;
			}

		// If we have a curHTTP.dynVarName then change state
		if(curHTTP.dynVarName[0] != '\0')
		{
			smHTTP = SM_HTTP_SEND_FROM_CALLBACK;
			curHTTP.callbackPos = 0;
		}

	}
    return FALSE;
}

Replace HTTPIncFile function with:

void HTTPIncFile(BYTE* cFile)
{
	WORD wCount, wLen;
	BYTE data[64];
	MPFS_HANDLE fp;

	// Check if this is a first round call
	if(curHTTP.callbackPos == 0x00u)
	{// On initial call, open the file and save its ID
		fp = MPFSOpen(cFile);
		if(fp == MPFS_INVALID_HANDLE)
		{// File not found, so abort
			return;
		}
		((DWORD_VAL*)&curHTTP.callbackPos)->w[0] = MPFSGetID(fp);
	}
	else
	{// The file was already opened, so load up its ID and seek
		fp = MPFSOpenID(((DWORD_VAL*)&curHTTP.callbackPos)->w[0]);
		if(fp == MPFS_INVALID_HANDLE)
		{// No file handles available, so wait for now
			return;
		}
		MPFSSeek(fp, ((DWORD_VAL*)&curHTTP.callbackPos)->w[1], MPFS_SEEK_FORWARD);
	}

	// Get/put as many bytes as possible
	wCount = TCPIsPutReady(sktHTTP);
	while(wCount > 0u)
	{
		wLen = MPFSGetArray(fp, data, mMIN(wCount, sizeof(data)));
		if(wLen == 0u)
		{// If no bytes were read, an EOF was reached
			MPFSClose(fp);
			curHTTP.callbackPos = 0x00;
			return;
		}
		else
		{// Write the bytes to the socket
			TCPPutArray(sktHTTP, data, wLen);
			wCount -= wLen;
		}
	}

	// Save the new address and close the file
	((DWORD_VAL*)&curHTTP.callbackPos)->w[1] = MPFSTell(fp);
	MPFSClose(fp);

	return;
}

Include 2 new files in your project (together they replace HTTPPrint.h)

H TTPDynVar.h:

/**************************************************************
Plug And Program 30/12/2010
Replaces HTTPPrint.H
For new dynamic variables:
- put associated function prototype in HTTPDynVar.h
- put the call to the associated function & parsing of paramters in HTTPDynVar.c
- put the associated function in CustomHTTPApp.c
**************************************************************/

#ifndef __HTTPDYNVAR_H
#define __HTTPDYNVAR_H

//#include "TCPIP Stack/TCPIP.h"

#if defined(STACK_USE_HTTP2_SERVER)

void HTTPDynVar(BYTE* dynVarName, BYTE* dynVarParam);

void HTTPPrint_hellomsg(void);
void HTTPPrint_cookiename(void);
void HTTPPrint_(void);
void HTTPPrint_builddate(void);
void HTTPPrint_led(WORD);
void HTTPPrint_lcdtext(void);
void HTTPPrint_ledSelected(WORD,WORD);
void HTTPPrint_version(void);
void HTTPPrint_btn(WORD);
void HTTPPrint_uploadedmd5(void);
void HTTPPrint_status_ok(void);
void HTTPPrint_ddns_status(void);
void HTTPPrint_ddns_status_msg(void);
void HTTPPrint_ddns_user(void);
void HTTPPrint_ddns_pass(void);
void HTTPPrint_ddns_host(void);
void HTTPPrint_status_fail(void);
void HTTPPrint_config_mac(void);
void HTTPPrint_config_hostname(void);
void HTTPPrint_config_dhcpchecked(void);
void HTTPPrint_config_ip(void);
void HTTPPrint_config_gw(void);
void HTTPPrint_config_subnet(void);
void HTTPPrint_config_dns1(void);
void HTTPPrint_config_dns2(void);
void HTTPPrint_reboot(void);
void HTTPPrint_rebootaddr(void);
void HTTPPrint_ddns_service(WORD);
void HTTPPrint_read_comm(WORD);
void HTTPPrint_write_comm(WORD);
void HTTPPrint_smtps_en(void);
void HTTPPrint_snmp_en(void);

#endif

#endif

HTTPDynVar.c


/**************************************************************
Plug And Program 30/12/2010
Replaces HTTPPrint.H
For new dynamic variables:
- put associated function prototype in HTTPDynVar.h
- put the call to the associated function & parsing of paramters in HTTPDynVar.c
- put the associated function in HTTPDynVar.c or CustomHTTPApp.c
  per your preference but in HTTPDynVar.c results in less effort to migrate to a
  new stack version
**************************************************************/
//#include "TCPIP Stack/TCPIP.h"
#include "HTTPDynVar.h"

void HTTPDynVar(BYTE* dynVarName, BYTE* dynVarParam)
{
	//Check for each known dynamic variable name
	//If a match, parse parameters if necessary and call relevant function
	//For performance: put shorter and most frequently called Dynamic Variables at the
	//top inside each case block

	BYTE dynVarNameLen;
	dynVarNameLen = strlen((BYTE*) dynVarName);

	switch(dynVarNameLen)
	{
        case 2:
		{
			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"an"))
			{
				HTTPPrint_an((BYTE)atoi(dynVarParam));
				return;
			}

			break;
		}

        case 3:
		{
			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"inc"))
			{
				HTTPIncFile(dynVarParam);
				return;
			}

			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"led"))
			{
				HTTPPrint_led((BYTE)atoi(dynVarParam));
				return;
			}

			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"es1"))
			{
				HTTPPrint_es1((BYTE)atoi(dynVarParam));
				return;
			}	

			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"btn"))
			{
				HTTPPrint_btn(atoi(dynVarParam));
				return;
			}

			break;
		}

        case 4:
		{

			break;
		}

        case 5:
		{
			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"ledmb"))
			{
				HTTPPrint_ledmb();
				return;
			}
			break;
		}

        case 6:
		{
			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"reboot"))
			{
				HTTPPrint_reboot();
				return;
			}

			break;
		}

        case 7:
		{
			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"ledtris"))
			{
				HTTPPrint_ledtris((BYTE)atoi(dynVarParam));
				return;
			}

			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"lcdtext"))
			{
				HTTPPrint_lcdtext();
				return;
			}

			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"version"))
			{
				HTTPPrint_version();
				return;
			}

			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"snmp_en"))
			{
				HTTPPrint_snmp_en();
				return;
			}

			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"ntptime"))
			{
				HTTPPrint_ntptime();
				return;
			}

			break;
		}

        case 8:
		{
			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"hellomsg"))
			{
				HTTPPrint_hellomsg();
				return;
			}

			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"smtps_en"))
			{
				HTTPPrint_smtps_en();
				return;
			}

			break;
		}

        case 9:
		{

			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"builddate"))
			{
				HTTPPrint_builddate();
				return;
			}

			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"status_ok"))
			{
				HTTPPrint_status_ok();
				return;
			}

			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"ddns_user"))
			{
				HTTPPrint_ddns_user();
				return;
			}

			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"ddns_pass"))
			{
				HTTPPrint_ddns_pass();
				return;
			}

			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"ddns_host"))
			{
				HTTPPrint_ddns_host();
				return;
			}

			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"config_ip"))
			{
				HTTPPrint_config_ip();
				return;
			}

			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"config_gw"))
			{
				HTTPPrint_config_gw();
				return;
			}

			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"read_comm"))
			{
				HTTPPrint_read_comm(atoi(dynVarParam));
				return;
			}

			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"listfiles"))
			{
				HTTPPrint_listfiles(dynVarParam);
				return;
			}

			break;
		}

        case 10:
		{
			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"cookiename"))
			{
				HTTPPrint_cookiename();
				return;
			}

			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"config_mac"))
			{
				HTTPPrint_config_mac();
				return;
			}

			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"rebootaddr"))
			{
				HTTPPrint_rebootaddr();
				return;
			}

			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"write_comm"))
			{
				HTTPPrint_write_comm(atoi(dynVarParam));
				return;
			}

			break;
		}

        case 11:
		{
			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"ledSelected"))
			{
				// 2 part parameter
				HTTPPrint_ledSelected((BYTE)atoi(dynVarParam),strstr(dynVarParam, (ROM BYTE*)"TRUE") != NULL ? TRUE : FALSE);
				return;
			}

			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"uploadedmd5"))
			{
				HTTPPrint_uploadedmd5();
				return;
			}

			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"ddns_status"))
			{
				HTTPPrint_ddns_status();
				return;
			}

			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"status_fail"))
			{
				HTTPPrint_status_fail();
				return;
			}

			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"config_dns1"))
			{
				HTTPPrint_config_dns1();
				return;
			}

			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"config_dns2"))
			{
				HTTPPrint_config_dns2();
				return;
			}

			break;
		}

        case 12:
		{
			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"ddns_service"))
			{
				HTTPPrint_ddns_service(atoi(dynVarParam));
				return;
			}
			break;
		}

        case 13:
		{
			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"config_subnet"))
			{
				HTTPPrint_config_subnet();
				return;
			}

			break;
		}

        case 14:
		{

			break;
		}

        case 15:
		{
			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"ddns_status_msg"))
			{
				HTTPPrint_ddns_status_msg();
				return;
			}

			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"config_hostname"))
			{
				HTTPPrint_config_hostname();
				return;
			}
			break;
		}

        case 18:
		{
			if(!stricmppgm2ram((BYTE*)dynVarName,(ROM BYTE*)"config_dhcpchecked"))
			{
				HTTPPrint_config_dhcpchecked();
				return;
			}

			break;
		}

	}

	// Output notification for undefined values
	TCPPutROMArray(sktHTTP, (ROM BYTE*)"!DEF", 4);

	return;
}

void HTTPPrint_an(WORD num)
{
	// PlugAndProgram.com 20110101
    BYTE AN0String[8];
    WORD ADval;
    // Determine which AN channel
    switch(num)
    {
        case 0:
            ADval = (WORD)ADC1BUF0;
            break;
        case 1:
            ADval = (WORD)ADC1BUF1;
            break;
        case 2:
            ADval = (WORD)ADC1BUF2;
            break;
        case 3:
            ADval = (WORD)ADC1BUF3;
            break;
        case 4:
            ADval = (WORD)ADC1BUF4;
            break;
        case 5:
            ADval = (WORD)ADC1BUF5;
            break;
        case 6:
            ADval = (WORD)ADC1BUF6;
            break;
        case 7:
            ADval = (WORD)ADC1BUF7;
            break;

        default:
            ADval = 0;
    }

    // Print the output
    uitoa(ADval, (BYTE*)AN0String);
    TCPPutString(sktHTTP, AN0String);
}

void HTTPPrint_es1(WORD num)
{
	// PlugAndProgram.com 20110101
    BYTE AN0String[8];
    WORD ADval;

    // Determine which AN channel
    switch(num)
    {
        case 0:
            // Temperature MCP9700AT
            ADval = (DWORD)ADC1BUF1;
            ADval = (WORD)((WORD)((DWORD)( ADval /1024 ) *3300 ) - 500 ) / 10;
            // Print the output
            uitoa(ADval, (BYTE*)AN0String);
            TCPPutString(sktHTTP, AN0String);
            break;

        case 1:
            // Ambient light sensor
            ADval = (WORD)ADC1BUF0/100;
            // Print the output
            uitoa(ADval, (BYTE*)AN0String);
            TCPPutString(sktHTTP, AN0String);
            break;

        case 2:
            // MP3H6115A Pressure sensor
            ADval = (WORD)ADC1BUF2;
            //ADval = (((ADval / 1023) + 0.095 ) / 0.009);
            ADval = (WORD)(DWORD)(((ADval * 10000) / 1024 ) + 950 ) / 90;
            // Print the output
            uitoa(ADval, (BYTE*)AN0String);
            TCPPutString(sktHTTP, AN0String);
            break;

        case 3:

            // HIH-5030 Humidity Sensor
            ADval = (WORD)ADC1BUF3;
            ADval = (WORD)(DWORD)(((ADval * 100000) / 1024 ) - 15150 ) / 636;
            // Print the output
            uitoa(ADval, (BYTE*)AN0String);
            TCPPutString(sktHTTP, AN0String);
            break;

        default:
            ADval = 0;
            // Print the output
            uitoa(ADval, (BYTE*)AN0String);
            TCPPutString(sktHTTP, AN0String);
    }
}

void HTTPPrint_ledmb(void)
{
	// PlugAndProgram.com 20110101
	// Print the output
	TCPPut(sktHTTP, (MBLEDTACTLAT?'1':'0'));
	return;
}

void HTTPPrint_ledtris(WORD num)
{
	// PlugAndProgram.com 20110101
	// Determine which LED
	switch(num)
	{
		case 0:
			num = (LED0_TRIS == 1) ? 2 : LED0_IO ;
			break;
		case 1:
			num = (LED1_TRIS == 1) ? 2 : LED1_IO ;
			break;
		case 2:
			num = (LED2_TRIS == 1) ? 2 : LED2_IO ;
			break;
		case 3:
			num = (LED3_TRIS == 1) ? 2 : LED3_IO ;
			break;
		case 4:
			num = (LED4_TRIS == 1) ? 2 : LED4_IO ;
			break;
		case 5:
			num = (LED5_TRIS == 1) ? 2 : LED5_IO ;
			break;
		case 6:
			num = (LED6_TRIS == 1) ? 2 : LED6_IO ;
			break;
		case 7:
			num = (LED7_TRIS == 1) ? 2 : LED7_IO ;
			break;

		default:
			num = 2;
	}

	// Print the output
	TCPPut(sktHTTP, ((num == 2) ? '2' : (num == 1 ? '1': '0')));
	return;
}

void HTTPPrint_listfiles(BYTE* source)
{
	// PlugAndProgram.com 20110101
	DWORD currentMPFSHandle;
	BYTE fileName[64];
	BYTE fileNameLen;

	// PlugAndProgram.com 20110101
	// Get name for every fatID from 0 up until we get a no reply
	// retrieve 1 fatID at a time
	// track next fatID in curHTTP.callbackPos
	// curHTTP.callbackPos is initialised at 00u for a new callback but needs to be other
	// than 0 for the callback to be treated as incomplete in the HTTP2 state SM_HTTP_SEND_FROM_CALLBACK
	// so we will use curHTTP.callbackPos to keep track of the next fatID as fatID + 1

	// We output an HTML A ref
	//	<a href="/filename">/filename</a></br>
	//	File names are 64 bits, so we need to ensure we have 10 + 63 + 3 + 63 + 9 = 148 chars
	//  space in TCPPutArray before we output this entry

	if (curHTTP.callbackPos == 0)
	{
		// First time in listfiles for this callback
		curHTTP.callbackPos = 1;

		// Uncomment this if you are using 2 MPFS images
		if(*source == 'I' || *source == 'i')
			{
			// List from internal memory
			curHTTP.callbackPos += INTERNALFATIDOFFSET;
			}
	}

	if (TCPIsPutReady(sktHTTP) > 148)
	{
		if ((currentMPFSHandle = MPFSOpenID(curHTTP.callbackPos - 1)) != MPFS_INVALID_HANDLE)
		{

			if (MPFSGetFilename(currentMPFSHandle, fileName, 64))
			{
				fileNameLen = strlen(fileName);
				// Len can be 0 if is MPFS2 file system dynamic variable index file
				if (fileNameLen > 0)
				{
					TCPPutArray(sktHTTP, "<a href=\"/", 10u);
					TCPPutArray(sktHTTP, fileName, fileNameLen);
					TCPPutArray(sktHTTP, "\">/", 3u);
					TCPPutArray(sktHTTP, fileName, fileNameLen);
					TCPPutArray(sktHTTP, "</a></br>", 9u);
				}
			}
			MPFSClose(currentMPFSHandle);
			curHTTP.callbackPos++;
		}
		else
		{
			// No more fatID's to process so close the callback
			curHTTP.callbackPos = 0;
		}
	}
	return;
}

void HTTPPrint_ntptime(void)
{
	DWORD time = SNTPGetUTCSeconds();
	// PlugAndProgram.com 20110101
	// Print the output if it has been received from NTP server
	if (time != NULL)
	{
		TCPPutString(sktHTTP, ctime(&time));
	}
}

Updated 20101231