//*****************************************************************************
//
// enet_io.c - I/O control via a web server.
//
// Copyright (c) 2007-2017 Texas Instruments Incorporated.  All rights reserved.
// Software License Agreement
// 
// Texas Instruments (TI) is supplying this software for use solely and
// exclusively on TI's microcontroller products. The software is owned by
// TI and/or its suppliers, and is protected under applicable copyright
// laws. You may not combine this software with "viral" open-source
// software in order to form a larger program.
// 
// THIS SOFTWARE IS PROVIDED "AS IS" AND WITH ALL FAULTS.
// NO WARRANTIES, WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING, BUT
// NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE APPLY TO THIS SOFTWARE. TI SHALL NOT, UNDER ANY
// CIRCUMSTANCES, BE LIABLE FOR SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
// DAMAGES, FOR ANY REASON WHATSOEVER.
// 
// This is part of revision 2.1.4.178 of the DK-TM4C129X Firmware Package.
//
//*****************************************************************************
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "inc/hw_ints.h"
#include "inc/hw_memmap.h"
#include "inc/hw_nvic.h"
#include "inc/hw_types.h"
#include "driverlib/flash.h"
#include "driverlib/gpio.h"
#include "driverlib/interrupt.h"
#include "driverlib/sysctl.h"
#include "driverlib/systick.h"
#include "driverlib/timer.h"
#include "driverlib/rom.h"
#include "driverlib/rom_map.h"
#include "utils/locator.h"
#include "utils/lwiplib.h"
#include "utils/uartstdio.h"
#include "utils/ustdlib.h"
#include "httpserver_raw/httpd.h"
#include "grlib/grlib.h"
#include "drivers/kentec320x240x16_ssd2119.h"
#include "drivers/pinout.h"
#include "io.h"
#include "cgifuncs.h"
#include "drivers/frame.h"

//*****************************************************************************
//
//! \addtogroup example_list
//! <h1>Ethernet-based I/O Control (enet_io)</h1>
//!
//! This example application demonstrates web-based I/O control using the
//! Tiva Ethernet controller and the lwIP TCP/IP Stack.  DHCP is used to
//! obtain an Ethernet address.  If DHCP times out without obtaining an
//! address, a static IP address will be chosen using AutoIP.  The address that
//! is selected will be shown on the QVGA display allowing you to access the
//! internal web pages served by the application via your normal web browser.
//!
//! Two different methods of controlling board peripherals via web pages are
//! illustrated via pages labeled ``IO Control Demo 1'' and ``IO Control
//! Demo 2'' in the navigation menu on the left of the application's home page.
//! In both cases, the example allows you to toggle the state of the user LED
//! on the board and set the speed of a graphical animation on the display.
//!
//! ``IO Control Demo 1'' uses JavaScript running in the web browser to send
//! HTTP requests for particular special URLs.  These special URLs are
//! intercepted in the file system support layer (io_fs.c) and used to
//! control the LED and animation.  Responses generated by the board are
//! returned to the browser and inserted into the page HTML dynamically by
//! more JavaScript code.
//!
//! ``IO Control Demo 2'' uses standard HTML forms to pass parameters to CGI
//! (Common Gateway Interface) handlers running on the board.  These handlers
//! process the form data and control the animation and LED as requested before
//! sending a response page (in this case, the original form) back to the
//! browser.  The application registers the names and handlers for each of its
//! CGIs with the HTTPD server during initialization and the server calls
//! these handlers after parsing URL parameters each time one of the CGI URLs
//! is requested.
//!
//! Information on the state of the various controls in the second demo is
//! inserted into the served HTML using SSI (Server Side Include) tags which
//! are parsed by the HTTPD server in the application.  As with the CGI
//! handlers, the application registers its list of SSI tags and a handler
//! function with the web server during initialization and this handler is
//! called whenever any registered tag is found in a .shtml, .ssi, .shtm or
//! .xml file being served to the browser.
//!
//! In addition to LED and animation speed control, the second example also
//! allows a line of text to be sent to the board for display on the LCD
//! panel.  This is included to illustrate the decoding of HTTP text strings.
//!
//! Note that the web server used by this example has been modified from the
//! example shipped with the basic lwIP package.  Additions include SSI and
//! CGI support along with the ability to have the server automatically insert
//! the HTTP headers rather than having these built in to the files in the
//! file system image.
//!
//! Source files for the internal file system image can be found in the ``fs''
//! directory.  If any of these files are changed, the file system image
//! (io_fsdata.h) should be rebuilt by running the following command from the
//! enet_io directory:
//!
//! ../../../../tools/bin/makefsfile -i fs -o io_fsdata.h -r -h -q
//!
//! For additional details on lwIP, refer to the lwIP web page at:
//! http://savannah.nongnu.org/projects/lwip/
//
//*****************************************************************************

//*****************************************************************************
//
// Defines for setting up the system clock.
//
//*****************************************************************************
#define SYSTICKHZ               100
#define SYSTICKMS               (1000 / SYSTICKHZ)

//*****************************************************************************
//
// Interrupt priority definitions.  The top 3 bits of these values are
// significant with lower values indicating higher priority interrupts.
//
//*****************************************************************************
#define SYSTICK_INT_PRIORITY    0x80
#define ETHERNET_INT_PRIORITY   0xC0

//*****************************************************************************
//
// A set of flags.  The flag bits are defined as follows:
//
//     0 -> An indicator that the animation timer interrupt has occurred.
//
//*****************************************************************************
#define FLAG_TICK            0
static volatile unsigned long g_ulFlags;

//*****************************************************************************
//
// The graphics context used by the application.
//
//*****************************************************************************
tContext g_sContext;

//*****************************************************************************
//
// The Y extent of the box used to display the text string sent from the
// remote web browser.
//
//*****************************************************************************
#define TEXT_BOX_TOP        140
#define TEXT_BOX_BOTTOM     184

//*****************************************************************************
//
// The position and size of the box used to display the simple animation.
//
//*****************************************************************************
#define ANIM_TOP            210
#define ANIM_HEIGHT         20
#define ANIM_LEFT           40
#define ANIM_WIDTH          (320 - (2 * ANIM_LEFT))

//*****************************************************************************
//
// The RGB value we use to change the color each time the animated bar cycles
// back to the start.
//
//*****************************************************************************
#define COLOR_INCREMENT     0x00306040

//*****************************************************************************
//
// Position and movement granularity for the status indicator shown while
// the IP address is being determined.
//
//*****************************************************************************
#define STATUS_X     40
#define STATUS_Y     50
#define MAX_STATUS_X (320 - (2 * STATUS_X))
#define ANIM_STEP_SIZE   8

//*****************************************************************************
//
// External Application references.
//
//*****************************************************************************
extern void httpd_init(void);

//*****************************************************************************
//
// SSI tag indices for each entry in the g_pcSSITags array.
//
//*****************************************************************************
#define SSI_INDEX_LEDSTATE  0
#define SSI_INDEX_FORMVARS  1
#define SSI_INDEX_SPEED     2

//*****************************************************************************
//
// This array holds all the strings that are to be recognized as SSI tag
// names by the HTTPD server.  The server will call SSIHandler to request a
// replacement string whenever the pattern <!--#tagname--> (where tagname
// appears in the following array) is found in ".ssi", ".shtml" or ".shtm"
// files that it serves.
//
//*****************************************************************************
static const char *g_pcConfigSSITags[] =
{
    "LEDtxt",        // SSI_INDEX_LEDSTATE
    "FormVars",      // SSI_INDEX_FORMVARS
    "speed"          // SSI_INDEX_SPEED
};

//*****************************************************************************
//
// The number of individual SSI tags that the HTTPD server can expect to
// find in our configuration pages.
//
//*****************************************************************************
#define NUM_CONFIG_SSI_TAGS     (sizeof(g_pcConfigSSITags) / sizeof (char *))

//*****************************************************************************
//
// Prototypes for the various CGI handler functions.
//
//*****************************************************************************
static char *ControlCGIHandler(int32_t iIndex, int32_t i32NumParams, char *pcParam[],
                              char *pcValue[]);
static char *SetTextCGIHandler(int32_t iIndex, int32_t i32NumParams, char *pcParam[],
                              char *pcValue[]);

//*****************************************************************************
//
// Prototype for the main handler used to process server-side-includes for the
// application's web-based configuration screens.
//
//*****************************************************************************
static int32_t SSIHandler(int32_t iIndex, char *pcInsert, int32_t iInsertLen);

//*****************************************************************************
//
// CGI URI indices for each entry in the g_psConfigCGIURIs array.
//
//*****************************************************************************
#define CGI_INDEX_CONTROL       0
#define CGI_INDEX_TEXT          1

//*****************************************************************************
//
// This array is passed to the HTTPD server to inform it of special URIs
// that are treated as common gateway interface (CGI) scripts.  Each URI name
// is defined along with a pointer to the function which is to be called to
// process it.
//
//*****************************************************************************
static const tCGI g_psConfigCGIURIs[] =
{
    { "/iocontrol.cgi", (tCGIHandler)ControlCGIHandler }, // CGI_INDEX_CONTROL
    { "/settxt.cgi", (tCGIHandler)SetTextCGIHandler }     // CGI_INDEX_TEXT
};

//*****************************************************************************
//
// The number of individual CGI URIs that are configured for this system.
//
//*****************************************************************************
#define NUM_CONFIG_CGI_URIS     (sizeof(g_psConfigCGIURIs) / sizeof(tCGI))

//*****************************************************************************
//
// The file sent back to the browser by default following completion of any
// of our CGI handlers.  Each individual handler returns the URI of the page
// to load in response to it being called.
//
//*****************************************************************************
#define DEFAULT_CGI_RESPONSE    "/io_cgi.ssi"

//*****************************************************************************
//
// The file sent back to the browser in cases where a parameter error is
// detected by one of the CGI handlers.  This should only happen if someone
// tries to access the CGI directly via the broswer command line and doesn't
// enter all the required parameters alongside the URI.
//
//*****************************************************************************
#define PARAM_ERROR_RESPONSE    "/perror.htm"

#define JAVASCRIPT_HEADER                                                     \
    "<script type='text/javascript' language='JavaScript'><!--\n"
#define JAVASCRIPT_FOOTER                                                     \
    "//--></script>\n"

//*****************************************************************************
//
// Timeout for DHCP address request (in seconds).
//
//*****************************************************************************
#ifndef DHCP_EXPIRE_TIMER_SECS
#define DHCP_EXPIRE_TIMER_SECS  45
#endif

//*****************************************************************************
//
// The positions of the circles in the animation used while waiting for an IP
// address.
//
//*****************************************************************************
const int32_t g_ppi32CirclePos[][2] =
{
    {
        12, 0
    },
    {
        8, -9
    },
    {
        0, -12
    },
    {
        -8, -9
    },
    {
        -12, 0
    },
    {
        -8, 9
    },
    {
        0, 12
    },
    {
        8, 9
    }
};

//*****************************************************************************
//
// The colors of the circles in the animation used while waiting for an IP
// address.
//
//*****************************************************************************
const uint32_t g_pui32CircleColor[] =
{
    0x111111,
    0x333333,
    0x555555,
    0x777777,
    0x999999,
    0xbbbbbb,
    0xdddddd,
    0xffffff,
};

//*****************************************************************************
//
// The current color index for the animation used while waiting for an IP
// address.
//
//*****************************************************************************
uint32_t g_ui32ColorIdx;

//*****************************************************************************
//
// The current IP address.
//
//*****************************************************************************
uint32_t g_ui32IPAddress;

//*****************************************************************************
//
// The application's graphics context.
//
//*****************************************************************************
tContext g_sContext;

//*****************************************************************************
//
// The system clock frequency.  Used by the SD card driver.
//
//*****************************************************************************
uint32_t g_ui32SysClock;


//*****************************************************************************
//
// The error routine that is called if the driver library encounters an error.
//
//*****************************************************************************
#ifdef DEBUG
void
__error__(char *pcFilename, uint32_t ui32Line)
{
}
#endif

//*****************************************************************************
//
// This CGI handler is called whenever the web browser requests iocontrol.cgi.
//
//*****************************************************************************
static char *
ControlCGIHandler(int32_t iIndex, int32_t i32NumParams, char *pcParam[], char *pcValue[])
{
    int32_t i32LEDState, i32Speed;
    bool bParamError;

    //
    // We have not encountered any parameter errors yet.
    //
    bParamError = false;

    //
    // Get each of the expected parameters.
    //
    i32LEDState = FindCGIParameter("LEDOn", pcParam, i32NumParams);
    i32Speed = GetCGIParam("speed_percent", pcParam, pcValue, i32NumParams,
                         &bParamError);

    //
    // Was there any error reported by the parameter parser?
    //
    if(bParamError || (i32Speed < 0) || (i32Speed > 100))
    {
        return(PARAM_ERROR_RESPONSE);
    }

    //
    // We got all the parameters and the values were within the expected ranges
    // so go ahead and make the changes.
    //
    io_set_led((i32LEDState == -1) ? false : true);
    io_set_animation_speed(i32Speed);

    //
    // Send back the default response page.
    //
    return(DEFAULT_CGI_RESPONSE);
}

//*****************************************************************************
//
// This CGI handler is called whenever the web browser requests settxt.cgi.
//
//*****************************************************************************
static char *
SetTextCGIHandler(int32_t i32Index, int32_t i32NumParams, char *pcParam[], char *pcValue[])
{
    long lStringParam;
    char pcDecodedString[48];
    tRectangle sRect;

    //
    // Find the parameter that has the string we need to display.
    //
    lStringParam = FindCGIParameter("DispText", pcParam, i32NumParams);

    //
    // If the parameter was not found, show the error page.
    //
    if(lStringParam == -1)
    {
        return(PARAM_ERROR_RESPONSE);
    }

    //
    // The parameter is present. We need to decode the text for display.
    //
    DecodeFormString(pcValue[lStringParam], pcDecodedString, 48);

    //
    // Erase the previous string and overwrite it with the new one.
    //
    sRect.i16XMin = 15;
    sRect.i16XMax = GrContextDpyWidthGet(&g_sContext) - 15 -1 ;
    sRect.i16YMin = TEXT_BOX_TOP + 6;
    sRect.i16YMax = TEXT_BOX_BOTTOM - 2;
    GrContextForegroundSet(&g_sContext, ClrBlack);
    GrRectFill(&g_sContext, &sRect);

    //
    // Now draw the new string after setting the clipping rectangle.
    //
    GrContextClipRegionSet(&g_sContext, &sRect);
    GrContextForegroundSet(&g_sContext, ClrYellow);
    GrStringDrawCentered(&g_sContext, pcDecodedString, -1,
                         (sRect.i16XMin + sRect.i16XMax) / 2,
                         (sRect.i16YMin + sRect.i16YMax) / 2, 0);

    //
    // Reset the clipping rectangle to the full screen.
    //
    sRect.i16XMin = 0;
    sRect.i16XMax = GrContextDpyWidthGet(&g_sContext) - 1;
    sRect.i16YMin = 0;
    sRect.i16YMax = GrContextDpyHeightGet(&g_sContext) - 1;
    GrContextClipRegionSet(&g_sContext, &sRect);

    //
    // Tell the HTTPD server which file to send back to the client.
    //
    return(DEFAULT_CGI_RESPONSE);
}

//*****************************************************************************
//
// This function is called by the HTTP server whenever it encounters an SSI
// tag in a web page.  The iIndex parameter provides the index of the tag in
// the g_pcConfigSSITags array. This function writes the substitution text
// into the pcInsert array, writing no more than iInsertLen characters.
//
//*****************************************************************************
static int32_t
SSIHandler(int32_t iIndex, char *pcInsert, int32_t iInsertLen)
{
    //
    // Which SSI tag have we been passed?
    //
    switch(iIndex)
    {
        case SSI_INDEX_LEDSTATE:
            io_get_ledstate(pcInsert, iInsertLen);
            break;

        case SSI_INDEX_FORMVARS:
            usnprintf(pcInsert, iInsertLen,
                      "%sls=%d;\nsp=%d;\n%s",
                      JAVASCRIPT_HEADER,
                      io_is_led_on(),
                      io_get_animation_speed(),
                      JAVASCRIPT_FOOTER);
            break;

        case SSI_INDEX_SPEED:
            io_get_animation_speed_string(pcInsert, iInsertLen);
            break;

        default:
            usnprintf(pcInsert, iInsertLen, "??");
            break;
    }

    //
    // Tell the server how many characters our insert string contains.
    //
    return(strlen(pcInsert));
}

//*****************************************************************************
//
// The interrupt handler for the SysTick interrupt.
//
//*****************************************************************************
void
SysTickIntHandler(void)
{
    //
    // Call the lwIP timer handler.
    //
    lwIPTimer(SYSTICKMS);
}

//*****************************************************************************
//
// The interrupt handler for the timer used to pace the animation.
//
//*****************************************************************************
void
AnimTimerIntHandler(void)
{
    //
    // Clear the timer interrupt.
    //
    ROM_TimerIntClear(TIMER2_BASE, TIMER_TIMA_TIMEOUT);

    //
    // Indicate that a timer interrupt has occurred.
    //
    HWREGBITW(&g_ulFlags, FLAG_TICK) = 1;
}

//*****************************************************************************
//
// Display an lwIP type IP Address.
//
//*****************************************************************************
void
DisplayIPAddress(uint32_t ui32Addr, uint32_t ui32Col, uint32_t ui32Row)
{
    char pcBuf[16];

    //
    // Convert the IP Address into a string.
    //
    usprintf(pcBuf, "%d.%d.%d.%d", ui32Addr & 0xff, (ui32Addr >> 8) & 0xff,
             (ui32Addr >> 16) & 0xff, (ui32Addr >> 24) & 0xff);

    //
    // Display the string.
    //
    GrStringDraw(&g_sContext, pcBuf, -1, ui32Col, ui32Row, false);
}

//*****************************************************************************
//
// Required by lwIP library to support any host-related timer functions.
//
//*****************************************************************************
void
lwIPHostTimerHandler(void)
{
    uint32_t ui32Idx, ui32NewIPAddress, ui32Width;
    tRectangle sRect;

    //
    // Get the width and height of the display.
    //
    ui32Width = GrContextDpyWidthGet(&g_sContext);

    //
    // Get the current IP address.
    //
    ui32NewIPAddress = lwIPLocalIPAddrGet();

    //
    // See if the IP address has changed.
    //
    if(ui32NewIPAddress != g_ui32IPAddress)
    {
        //
        // Clear the display.
        //
        sRect.i16XMin = STATUS_X;
        sRect.i16YMin = STATUS_Y - 22;
        sRect.i16XMax = MAX_STATUS_X;
        sRect.i16YMax = TEXT_BOX_TOP - 10;
        GrContextForegroundSet(&g_sContext, ClrBlack);
        GrRectFill(&g_sContext, &sRect);
        GrContextForegroundSet(&g_sContext, ClrWhite);

        //
        // See if there is an IP address assigned.
        //
        if(ui32NewIPAddress == 0xffffffff)
        {
            //
            // Indicate that there is no link.
            //
            GrStringDrawCentered(&g_sContext, "Waiting for link", -1,
                                 ui32Width / 2, STATUS_Y, false);
        }
        else if(ui32NewIPAddress == 0)
        {
            //
            // There is no IP address, so indicate that the DHCP process is
            // running.
            //
            GrStringDrawCentered(&g_sContext, "Waiting for IP address", -1,
                                 ui32Width / 2, STATUS_Y, false);
        }
        else
        {
            //
            // Display the new IP address information.
            //
            GrStringDraw(&g_sContext, "IP Address:", -1, 60, STATUS_Y,
                         false);
            GrStringDraw(&g_sContext, "Subnet Mask:", -1, 60, STATUS_Y + 20, false);
            GrStringDraw(&g_sContext, "Gateway:", -1, 60, STATUS_Y + 40, false);
            DisplayIPAddress(ui32NewIPAddress, ui32Width / 2, STATUS_Y);
            DisplayIPAddress(lwIPLocalNetMaskGet(), ui32Width / 2, STATUS_Y + 20);
            DisplayIPAddress(lwIPLocalGWAddrGet(), ui32Width / 2,
                             STATUS_Y + 40);
        }

        //
        // Save the new IP address.
        //
        g_ui32IPAddress = ui32NewIPAddress;
    }

    //
    // If there is not an IP address, draw the animated circle.
    //
    if((ui32NewIPAddress == 0) || (ui32NewIPAddress == 0xffffffff))
    {
        //
        // Loop through the circles in the animation.
        //
        for(ui32Idx = 0; ui32Idx < 8; ui32Idx++)
        {
            //
            // Draw this circle.
            //
            GrContextForegroundSet(&g_sContext,
                                   g_pui32CircleColor[(g_ui32ColorIdx +
                                                       ui32Idx) & 7]);
            GrCircleFill(&g_sContext,
                         (ui32Width / 2) + g_ppi32CirclePos[ui32Idx][0],
                         STATUS_Y + g_ppi32CirclePos[ui32Idx][1] + 44,
                         2);
        }

        //
        // Increment the color index.
        //
        g_ui32ColorIdx++;
    }
}

//*****************************************************************************
//
// This example demonstrates the use of the Ethernet Controller and lwIP
// TCP/IP stack to control various peripherals on the board via a web
// browser.
//
//*****************************************************************************
int
main(void)
{
    uint32_t ui32User0, ui32User1, ui32AnimPos, ui32Color;
    uint8_t pui8MACArray[8];
    tRectangle sRect;
    //
    // Run from the PLL at 120 MHz.
    //
    g_ui32SysClock = MAP_SysCtlClockFreqSet((SYSCTL_XTAL_25MHZ |
                                             SYSCTL_OSC_MAIN | SYSCTL_USE_PLL |
                                             SYSCTL_CFG_VCO_480), 120000000);

    //
    // Configure the device pins.
    //
    PinoutSet();

    //
    // Initialize the display driver.
    //
    Kentec320x240x16_SSD2119Init(g_ui32SysClock);

    //
    // Initialize the graphics context.
    //
    GrContextInit(&g_sContext, &g_sKentec320x240x16_SSD2119);

    //
    // Draw the application frame.
    //
    FrameDraw(&g_sContext, "enet-io");

    //
    // Configure SysTick for a periodic interrupt.
    //
    ROM_SysTickPeriodSet(g_ui32SysClock / SYSTICKHZ);
    ROM_SysTickEnable();
    ROM_SysTickIntEnable();

    //
    // Configure the hardware MAC address for Ethernet Controller filtering of
    // incoming packets.  The MAC address will be stored in the non-volatile
    // USER0 and USER1 registers.
    //
    ROM_FlashUserGet(&ui32User0, &ui32User1);
    if((ui32User0 == 0xffffffff) || (ui32User1 == 0xffffffff))
    {
        //
        // We should never get here.  This is an error if the MAC address has
        // not been programmed into the device.  Exit the program.
        //
        GrContextForegroundSet(&g_sContext, ClrRed);
        GrStringDrawCentered(&g_sContext, "MAC Address", -1,
                             GrContextDpyWidthGet(&g_sContext) / 2,
                             (GrContextDpyHeightGet(&g_sContext) / 2) - 4,
                             false);
        GrStringDrawCentered(&g_sContext, "Not Programmed!", -1,
                             GrContextDpyWidthGet(&g_sContext) / 2,
                             (GrContextDpyHeightGet(&g_sContext) / 2) + 16,
                             false);
        while(1)
        {
        }
    }

    //
    // Tell the user what we are doing just now.
    //
    GrStringDrawCentered(&g_sContext, "Waiting for IP", -1,
                         GrContextDpyWidthGet(&g_sContext) / 2,
                         STATUS_Y, false);

    //
    // Draw the box inside which a text string sent from browser will be
    // displayed.
    //
    sRect.i16XMin = 10;
    sRect.i16XMax = GrContextDpyWidthGet(&g_sContext) - sRect.i16XMin - 1;
    sRect.i16YMin = TEXT_BOX_TOP;
    sRect.i16YMax = TEXT_BOX_BOTTOM - 1;
    GrContextForegroundSet(&g_sContext, ClrWhite);
    GrRectDraw(&g_sContext, &sRect);
    GrContextFontSet(&g_sContext, g_psFontCmss18);
    GrStringDraw(&g_sContext, " Browser Text: ", -1, sRect.i16XMin + 6,
                 TEXT_BOX_TOP - 10, true);

    //
    // Draw the box inside which we will draw the animation.
    //
    sRect.i16XMin = ANIM_LEFT;
    sRect.i16XMax = (ANIM_LEFT + ANIM_WIDTH) - 1;
    sRect.i16YMin = ANIM_TOP;
    sRect.i16YMax = (ANIM_TOP + ANIM_HEIGHT) - 1;
    GrContextForegroundSet(&g_sContext, ClrWhite);
    GrRectDraw(&g_sContext, &sRect);

    //
    // Initialize the animation variables.
    //
    ui32AnimPos = ANIM_LEFT + 1;
    ui32Color = ClrRed;

    //
    // Convert the 24/24 split MAC address from NV ram into a 32/16 split
    // MAC address needed to program the hardware registers, then program
    // the MAC address into the Ethernet Controller registers.
    //
    pui8MACArray[0] = ((ui32User0 >>  0) & 0xff);
    pui8MACArray[1] = ((ui32User0 >>  8) & 0xff);
    pui8MACArray[2] = ((ui32User0 >> 16) & 0xff);
    pui8MACArray[3] = ((ui32User1 >>  0) & 0xff);
    pui8MACArray[4] = ((ui32User1 >>  8) & 0xff);
    pui8MACArray[5] = ((ui32User1 >> 16) & 0xff);

    //
    // Initialze the lwIP library, using DHCP.
    //
    lwIPInit(g_ui32SysClock, pui8MACArray, 0, 0, 0, IPADDR_USE_DHCP);

    //
    // Setup the device locator service.
    //
    LocatorInit();
    LocatorMACAddrSet(pui8MACArray);
    LocatorAppTitleSet("DK-TM4C129X enet_io");

    //
    // Initialize a sample httpd server.
    //
    httpd_init();

    //
    // Set the interrupt priorities.  We set the SysTick interrupt to a higher
    // priority than the Ethernet interrupt to ensure that the file system
    // tick is processed if SysTick occurs while the Ethernet handler is being
    // processed.  This is very likely since all the TCP/IP and HTTP work is
    // done in the context of the Ethernet interrupt.
    //
    ROM_IntPrioritySet(INT_EMAC0, ETHERNET_INT_PRIORITY);
    ROM_IntPrioritySet(FAULT_SYSTICK, SYSTICK_INT_PRIORITY);

    //
    // Pass our tag information to the HTTP server.
    //
    http_set_ssi_handler((tSSIHandler)SSIHandler, g_pcConfigSSITags,
                         NUM_CONFIG_SSI_TAGS);

    //
    // Pass our CGI handlers to the HTTP server.
    //
    http_set_cgi_handlers(g_psConfigCGIURIs, NUM_CONFIG_CGI_URIS);

    //
    // Initialize IO controls
    //
    io_init();

    //
    // Loop forever, processing the on-screen animation.  All other work is
    // done in the interrupt handlers.
    //
    while(1)
    {
        //
        // Wait for a new tick to occur.
        //
        while(!g_ulFlags)
        {
            //
            // Do nothing.
            //
        }

        //
        // Clear the flag now that we have seen it.
        //
        HWREGBITW(&g_ulFlags, FLAG_TICK) = 0;

        //
        // Update the bar position and check for wrap.
        //
        ui32AnimPos ++;
        if(ui32AnimPos == ((ANIM_LEFT + ANIM_WIDTH) - 1))
        {
            //
            // We've reached the right edge so change color and move back
            // to the left.
            //
            ui32AnimPos = ANIM_LEFT + 1;
            ui32Color += COLOR_INCREMENT;
        }

        //
        // Now we draw the new bar.  Note that we need to temporarily
        // disable the ethernet interrupt while doing this since we have
        // been naughty and set this app up so that it accesses the graphics
        // library from two different contexts.
        //
        ROM_IntDisable(INT_EMAC0);
        GrContextForegroundSet(&g_sContext, ui32Color);
        GrLineDrawV(&g_sContext, ui32AnimPos, (ANIM_TOP + 1),
                    (ANIM_TOP + ANIM_HEIGHT) - 2);
        ROM_IntEnable(INT_EMAC0);
    }
}
