Repository for import to local machine

Dependencies:   DMBasicGUI DMSupport

main.cpp

Committer:
jmitc91516
Date:
2017-07-31
Revision:
8:26e49e6955bd
Parent:
7:f0e645cf73a2

File content as of revision 8:26e49e6955bd:

#include "mbed.h"
#include "DMBoard.h"
#include "lpc_swim.h"
#include "lpc_swim_font.h"

#include <string.h>

#include <stdio.h>
#include <stdlib.h>

#include "GuiLib.h"
#include "GuiDisplay.h"

#include "USBHostGC.h"
#include "TouchPanelPageSelector.h"
#include "GCHeatControl.h"
#include "GetGCStatusLoop.h"
#include "GCComponentStatusColorArea.h"
#include "GCStateAndFaultCodes.h"
#include "ProgressBar.h"
#include "EasyGUITouchAreaIndices.h"
#include "NetworkParameters.h"
#include "ServiceInterval.h"
#include "SettingsHandler.h"
#include "GasCalibrationPageHandler.h"
#include "ColumnDHAutoCalibrationPageHandler.h"
#include "ColumnDHManualCalibrationPageHandler.h"
#include "ColumnDHSensorCalibrationPageHandler.h"
#include "ColumnDHPSUDACPageHandler.h"
#include "GasBackPressureDACPageHandler.h"
#include "GasChannelDACAndADCPageHandler.h"
#include "NudgeAndDampPageHandler.h"
#include "NumericKeypadPageHandler.h"
#include "EthernetKeypadPageHandler.h"
#include "DebugCommandsPageHandler.h"
#include "QSPIBitmap.h"
#include "DetectorIgnitionHandler.h"
#include "SwimDraw.h"
#include "USBHostGCUtilities.h"


#define BUILD_DATE "31 July 2017" // Copied to easyGUI variable "GuiVar_buildDate", displayed on both Settings pages (normal and Running).
                                  // *** MUST update for each 'delivered' build, MUST correspond with date on 'delivered' binary file ***
                                  //     This should be updated first thing every morning, and immediately after every 'delivery'
                                  //     (to e.g. "dd mmm yyyy #2"), so that it is ready for the next one.
                                  //     Also - 'deliver' the ZIP file produced by the 'Export Program' command (right-click on the project
                                  //     in 'Program Workspace' at the left of this screen), as well as the binary executable and the ReadMe.txt file,
                                  //     and now the EasyGUI project file as well.

// Defined in GuiDisplay.c - set the display frame address at runtime
extern "C" {
    void GuiDisplay_SetFrameAddress(void *newFrameAddress);
}

// Used by QSPIBitmaps class
bool qspiAlreadyFormatted = false;

//#define MEMORY_TEST_OCT_2016
#ifdef MEMORY_TEST_OCT_2016
const int clogLength = 1000;
char clogUpMemory[clogLength];
#endif // MEMORY_TEST_OCT_2016

/*
    This application provides a draft implementation of the LPC4088 user interface to the GC.
    It talks to the GC over a USB link.

    The associated easyGUI project is C:/easyGUI Projects/GC500_5inch.gui
    
    It is intended for use with the Embedded Artists LPC4088 board, 5 inch display.
    
    Note that the two most important functions in this application are:
        main (obviously) or, more specifically, getGCStatusLoop->MainLoopWithEthernet(), called from main
        TouchCallback, which handles the user's interaction with the LPC4088 touch screen
        
    Most other functions are (ultimately) called by those two.
*/

// Forward declarations
GuiConst_INTCOLOR SixteenBitColorValue(GuiConst_INT8U red, GuiConst_INT8U green, GuiConst_INT8U blue); 
void DrawHeatOnOffButton(void);
void SetupDoorActuatorCommandUserInterface(USBDeviceConnected* usbDevice, USBHostGC* usbHostGC, bool beforePageDisplay);


static bool canUnlockDoorActuators = true; // Set false if there is some reason why they cannot be unlocked even when the GC is idle
/*
    Function to gives the rest of the world access to the above flag
*/
void CanUnlockDoorActuators(bool value)
{
    canUnlockDoorActuators = value;
}


//#define ALLOW_DEBUG_PRINTS // Without this, our 'debug print' functions contain no code, and do nothing.
                           // So we do not have to remove all the debug prints throughout this application - 
                           // just comment out this #define.
                           // Note that we are currently *not* applying this to the code in the 'DrawErrorMessage()' function,
                           // which is only used here in main.cpp.


/*
    Code received from Embedded Artists, to cause the LPC4088 to reboot 
    (thus allowing us to restart the Ethernet connection with a different IP address)
*/
void reboot()
{
  /* Disable watchdog */
  LPC_WDT->MOD       = 0;
  LPC_WDT->TC        = 0xFF;

  /* Set WDT timeout to 0.1s using the 500kHz oscillator
     and the fixed pre-scaler of 4 */
  LPC_WDT->TC = ((500000 / 4) / 10);
  
  /* Make sure a watchdog timeout causes a reset */
  LPC_WDT->MOD |= (1<<1);
  
  /* Enable the watchdog */
  LPC_WDT->MOD |= (1<<0);
  
  /* Initial feed to start the watchdog */
  LPC_WDT->FEED = 0xAA;
  LPC_WDT->FEED = 0x55;

  /* Should reboot after 100ms */
  while(true);
}


// ** Start of timeout code to guard against multiple presses of the Heat On/Off button **
//    (which is in the same position as the Abort Run button)
Timeout heatOnOffTimeout;

bool heatOnOffAvailable = true;

void MakeHeatOnOffAvailableAgain(void)
{
    heatOnOffAvailable = true;
}

void StartHeatOnOffTimeout(void)
{
    heatOnOffAvailable = false;
    heatOnOffTimeout.attach(&MakeHeatOnOffAvailableAgain, 1.0); // Wait 1.0 sec before accepting touches again on Heat On/Off button
}
// ** End of Heat On/Off timeout code **

#define USE_HEAT_ONOFF_BUTTON_BITMAPS

//#define TURN_HEAT_OFF_ON_ABORT // Doing this may or may not be a good idea...

// These are 'global' - TouchCallback function, as well as main(), needs to access them
TouchPanelPageSelectors touchPanelPageSelectors;
GCHeatControl* theGCHeatControl;


GetGCStatusLoop* getGCStatusLoop = NULL;

HomePageGCComponentStatusColorAreas homePageGCComponentStatusColorAreas;
SingleGCComponentPageStatusColorAreas singleGCComponentPageStatusColorAreas;

#define USE_QSPI_BITMAPS
#ifdef USE_QSPI_BITMAPS

QSPIBitmaps qspiBitmaps;

#define USE_QSPI_BACKGROUND_BITMAPS

#endif // USE_QSPI_BITMAPS

//#define WANT_STATUS_RECTANGLE_ON_COLUMN_AUTO_CALIB_PAGE
//#define WANT_STATUS_RECTANGLE_ON_GAS_CALIB_PAGES
//#define WANT_DOOR_ACTUATOR_BUTTONS_ON_COLUMN_PAGES
//#define WANT_COMPONENT_ICON_ON_PROFILE_PAGES

/*
    Bodge so GuiDisplay.c ('easyGUIFixed' file) can call Thread::wait 
    (giving it an accurate millisecond timer)
*/
extern "C" {
    void EasyGUIWaitMs(GuiConst_INT32U msec)
    {
        Thread::wait(msec);
    }
}

/*
    Passed three 8-bit colour components - red, green and blue.
    
    Returns the corresponding 16-bit colour value (5 bits for red, 6 bits for green, 5 bits for blue).
*/
GuiConst_INTCOLOR SixteenBitColorValue(GuiConst_INT8U red, GuiConst_INT8U green, GuiConst_INT8U blue)
{
    // Make sure we don't have numeric overflow problems during the conversion
    GuiConst_INT32U red32   = red;
    GuiConst_INT32U green32 = green;
    GuiConst_INT32U blue32  = blue;
    
//#define REVERSE_RED_AND_BLUE
#ifdef REVERSE_RED_AND_BLUE
    GuiConst_INT32U rgb = (red32 << 16) | (green32 << 8) | blue32;
#else
    GuiConst_INT32U rgb = (blue32 << 16) | (green32 << 8) | red32;
#endif

    return GuiLib_RgbToPixelColor(rgb);
}

// DebugPrint without '#ifdef ALLOW_DEBUG_PRINTS'
void SpecialDebugPrint(char *stuffToPrint, GuiConst_INT16S X, GuiConst_INT16S Y)
{
    static int counter = 0;
    char buff[300];
    
    const GuiConst_INT16U fontNo = GuiFont_Helv1;
    
    GuiDisplay_Lock();

    // (Attempt to) clear previous strings from display
    sprintf(buff, "                                        ");
    GuiLib_DrawStr(
        X,                      //GuiConst_INT16S X,
        Y,                      //GuiConst_INT16S Y,
        fontNo,                 //GuiConst_INT16U FontNo,
        buff,                   //GuiConst_TEXT PrefixLocate *String,
        GuiLib_ALIGN_LEFT,      //GuiConst_INT8U Alignment, 
        GuiLib_PS_ON,           //GuiConst_INT8U PsWriting,
        GuiLib_TRANSPARENT_OFF, //GuiConst_INT8U Transparent,
        GuiLib_UNDERLINE_OFF,   //GuiConst_INT8U Underlining,
        0,                      //GuiConst_INT16S BackBoxSizeX,
        0,                      //GuiConst_INT16S BackBoxSizeY1,
        0,                      //GuiConst_INT16S BackBoxSizeY2,
        GuiLib_BBP_NONE,        //GuiConst_INT8U BackBorderPixels,
        SixteenBitColorValue(0, 0, 0xFF),  //GuiConst_INTCOLOR ForeColor,
        SixteenBitColorValue(0, 0xFF, 0)   //GuiConst_INTCOLOR BackColor
    );

    sprintf(buff, "%s [%d]", stuffToPrint, ++counter);

    GuiLib_DrawStr(
        X,                      //GuiConst_INT16S X,
        Y,                      //GuiConst_INT16S Y,
        fontNo,                 //GuiConst_INT16U FontNo,
        buff,                   //GuiConst_TEXT PrefixLocate *String,
        GuiLib_ALIGN_LEFT,      //GuiConst_INT8U Alignment, 
        GuiLib_PS_ON,           //GuiConst_INT8U PsWriting,
        GuiLib_TRANSPARENT_OFF, //GuiConst_INT8U Transparent,
        GuiLib_UNDERLINE_OFF,   //GuiConst_INT8U Underlining,
        0,                      //GuiConst_INT16S BackBoxSizeX,
        0,                      //GuiConst_INT16S BackBoxSizeY1,
        0,                      //GuiConst_INT16S BackBoxSizeY2,
        GuiLib_BBP_NONE,        //GuiConst_INT8U BackBorderPixels,
        SixteenBitColorValue(0, 0, 0xFF),  //GuiConst_INTCOLOR ForeColor,
        SixteenBitColorValue(0, 0xFF, 0)   //GuiConst_INTCOLOR BackColor
    );
    
    GuiLib_Refresh();

    GuiDisplay_Unlock();
}

/*
    Prints (i.e. displays) the specified text at the specified coordinates on the screen, 
    blue text on a green background, using easyGUI.
    
    Args are: null-terminated string to print, x coord, y coord.
    
    No return code.
*/
void DebugPrint(char *stuffToPrint, GuiConst_INT16S X, GuiConst_INT16S Y)
{
#ifdef ALLOW_DEBUG_PRINTS
    char buff[200];
    
    const GuiConst_INT16U fontNo = GuiFont_Helv1;
    
    GuiDisplay_Lock();

    // (Attempt to) clear previous strings from display
    sprintf(buff, "                                        ");
    GuiLib_DrawStr(
        X,                      //GuiConst_INT16S X,
        Y,                      //GuiConst_INT16S Y,
        fontNo,                 //GuiConst_INT16U FontNo,
        buff,                   //GuiConst_TEXT PrefixLocate *String,
        GuiLib_ALIGN_LEFT,      //GuiConst_INT8U Alignment, 
        GuiLib_PS_ON,           //GuiConst_INT8U PsWriting,
        GuiLib_TRANSPARENT_OFF, //GuiConst_INT8U Transparent,
        GuiLib_UNDERLINE_OFF,   //GuiConst_INT8U Underlining,
        0,                      //GuiConst_INT16S BackBoxSizeX,
        0,                      //GuiConst_INT16S BackBoxSizeY1,
        0,                      //GuiConst_INT16S BackBoxSizeY2,
        GuiLib_BBP_NONE,        //GuiConst_INT8U BackBorderPixels,
        SixteenBitColorValue(0, 0, 0xFF),  //GuiConst_INTCOLOR ForeColor,
        SixteenBitColorValue(0, 0xFF, 0)   //GuiConst_INTCOLOR BackColor
    );

    GuiLib_DrawStr(
        X,                      //GuiConst_INT16S X,
        Y,                      //GuiConst_INT16S Y,
        fontNo,                 //GuiConst_INT16U FontNo,
        stuffToPrint,           //GuiConst_TEXT PrefixLocate *String,
        GuiLib_ALIGN_LEFT,      //GuiConst_INT8U Alignment, 
        GuiLib_PS_ON,           //GuiConst_INT8U PsWriting,
        GuiLib_TRANSPARENT_OFF, //GuiConst_INT8U Transparent,
        GuiLib_UNDERLINE_OFF,   //GuiConst_INT8U Underlining,
        0,                      //GuiConst_INT16S BackBoxSizeX,
        0,                      //GuiConst_INT16S BackBoxSizeY1,
        0,                      //GuiConst_INT16S BackBoxSizeY2,
        GuiLib_BBP_NONE,        //GuiConst_INT8U BackBorderPixels,
        SixteenBitColorValue(0, 0, 0xFF),  //GuiConst_INTCOLOR ForeColor,
        SixteenBitColorValue(0, 0xFF, 0)   //GuiConst_INTCOLOR BackColor
    );
    
    GuiLib_Refresh();

    GuiDisplay_Unlock();
#endif //ALLOW_DEBUG_PRINTS
}

/*
    A 'wrapper' function for the above, so that the caller does not need access 
    to the easyGUI declarations

    Intended to be used as an 'extern' in other modules.
*/
void EasyGUIDebugPrint(char *stuffToPrint, short X, short Y)
{
#ifdef ALLOW_DEBUG_PRINTS
    DebugPrint(stuffToPrint, (GuiConst_INT16S) X, (GuiConst_INT16S) Y);
#endif // ALLOW_DEBUG_PRINTS
}

/*
    Version of the above that increments, and displays, a counter each time it is called -
    so the user can see if it is being called repeatedly, or if the application has simply hung
*/
void EasyGUIDebugPrintWithCounter(char *stuffToPrint, short X, short Y)
{
#ifdef ALLOW_DEBUG_PRINTS
    static int counter = 0;
    char buff[300];
    sprintf(buff, "%s [%d]", stuffToPrint, ++counter);
    
    DebugPrint(buff, (GuiConst_INT16S) X, (GuiConst_INT16S) Y);
#endif // ALLOW_DEBUG_PRINTS
}

extern "C" {
    void EasyGUIDebugPrintWithCounterCalledFromC(char *stuffToPrint, short X, short Y)
    {
        EasyGUIDebugPrintWithCounter(stuffToPrint, X, Y);
    }
}

/*
    Gets the GC status.
    
    Args are: a pointer to the USBHostGC instance that corresponds to the GC,
              a pointer to the USBDeviceConnected instance that (also) corresponds to the GC
              
    Returns the GC status as an integer - see the GC_STATE enumeration in GCStateAndFaultCodes.h
    for the possible values.
*/
int GetGCStatus(USBDeviceConnected* usbDevice, USBHostGC* usbHostGC)
{
    while(usbHostGC->ExecutingSetDeviceReport()) {}

    char response[50];
    int status;
    
    // Ensure we always have valid chars in the positions we are interested in,
    // in case we get "DNAK" or "EPKT" back
    response[6] = '0';
    response[7] = '0';
    
    usbHostGC->SetDeviceReport(usbDevice, "QSTA", response);
    // We expect a response like "DSTA00nn", where "nn" is the status.
    sscanf(&response[6], "%d", &status);

    return status;
}

/*
    Tells the caller whether or not the GC is ready to run
    
    Args are: a pointer to the USBHostGC instance that corresponds to the GC,
              a pointer to the USBDeviceConnected instance that (also) corresponds to the GC
              
    Returns true if the GC is ready to run, false if not.
*/
bool GCIsReadyToRun(USBDeviceConnected* usbDevice, USBHostGC* usbHostGC)
{
#ifdef USE_VERSION_102 // See GCStateAndFaultCodes.h
    return (GetGCStatus(usbDevice, usbHostGC) == GC_STATE_102_METHOD_READY_TO_RUN);
#else
    return (GetGCStatus(usbDevice, usbHostGC) == GC_STATE_READY_TO_RUN);
#endif
}

/*
    Tells the caller whether or not the GC is in the 'stabilising' state
    
    Args are: a pointer to the USBHostGC instance that corresponds to the GC,
              a pointer to the USBDeviceConnected instance that (also) corresponds to the GC
              
    Returns true if the GC is stabilising, false if not.
*/
bool GCIsStabilising(USBDeviceConnected* usbDevice, USBHostGC* usbHostGC)
{
#ifdef USE_VERSION_102 // See GCStateAndFaultCodes.h
    return (GetGCStatus(usbDevice, usbHostGC) == GC_STATE_102_METHOD_STABILISING);
#else
    return false; // No "stabilising" state before version 1.02
#endif
}

/*
    Tells the caller whether or not the GC is in the 'equilibrating' state
    
    Args are: a pointer to the USBHostGC instance that corresponds to the GC,
              a pointer to the USBDeviceConnected instance that (also) corresponds to the GC
              
    Returns true if the GC is equilibrating, false if not.
*/
bool GCIsEquilibrating(USBDeviceConnected* usbDevice, USBHostGC* usbHostGC)
{
#ifdef USE_VERSION_102 // See GCStateAndFaultCodes.h
    return (GetGCStatus(usbDevice, usbHostGC) == GC_STATE_102_METHOD_EQUILIBRATING);
#else
    return (GetGCStatus(usbDevice, usbHostGC) == GC_STATE_EQUILIBRATING);
#endif
}

/*
    Tells the caller whether or not the GC is running
    
    Args are: a pointer to the USBHostGC instance that corresponds to the GC,
              a pointer to the USBDeviceConnected instance that (also) corresponds to the GC
              
    Returns true if the GC is running, false if not.
*/
bool GCIsRunning(USBDeviceConnected* usbDevice, USBHostGC* usbHostGC)
{
    int gcStatus = GetGCStatus(usbDevice, usbHostGC);
    
#ifdef USE_VERSION_102 // See GCStateAndFaultCodes.h
    return ((gcStatus >= GC_STATE_102_METHOD_RUNNING_MINIMUM) && (gcStatus <= GC_STATE_102_METHOD_RUNNING_MAXIMUM));
#else
    return ((gcStatus >= GC_STATE_RUNNING_MINIMUM) && (gcStatus <= GC_STATE_RUNNING_MAXIMUM));
#endif
}

/*
    Pass the current value of 'GuiVar_columnMaxTemp2' to the GC as the maximum column temperature.
    Intended to be called by the NumericKeypadPageHandler if the user presses the Apply button
    when we are editing the maximum column temperature. We pass a pointer to this function
    to the NumericKeypadPageHandler instance when we start editing.
    
    See the definition of 'ApplyFunctionPtr' in NumericKeypadPageHandler.h
*/
void SetColumnMaxTempFromEasyGuiVariable(USBDeviceConnected* usbDevice, USBHostGC* usbHostGC)
{
    int maxTemp;
    sscanf(GuiVar_columnMaxTemp2, "%d", &maxTemp);
    
    char buff[40];
    sprintf(buff, "SCMX%.4d", maxTemp);
    
    while(usbHostGC->ExecutingSetDeviceReport()) {}

    char response[50];
    usbHostGC->SetDeviceReport(usbDevice, buff, response);
}


/*
    Draws the Run button in the correct place, in the correct enabled/disabled state.
    
    Arg is: true to display the button in an enabled state, false to display it disabled.
    
    No return code.
*/
void DrawRunButton(bool enabled)
{
#define USE_BITMAPS
#ifdef USE_BITMAPS
    GuiConst_INT8U bitmapIndex = GuiStruct_Bitmap_RunButtonBlank;
    if(enabled) {
        bitmapIndex = GuiStruct_Bitmap_RunButtonGreen;
    }
    GuiConst_INTCOLOR transparentColor = SixteenBitColorValue(255, 255, 255); // In the 'corners' of the bitmap
    
    // Hard coded coordinates copied from easyGUI (I have not found 
    // a way of getting them from the easyGUI code at runtime)
    GuiLib_ShowBitmap(bitmapIndex, 327, 170, transparentColor);

#else // Draw a 'button' that consists of a box containing the word 'Run'.

    // Black if enabled, grey if disabled
    GuiConst_INTCOLOR buttonColor = (enabled) ? 0 : SixteenBitColorValue(0x80, 0x80, 0x80);

    GuiConst_TEXT *buttonText = "Run";

    // These are hard-coded to match the corresponding definitions in easyGUI
    // (I have not found a way of getting these values from easyGUI at run time)
    const GuiConst_INT16S textX1 = 400;
    const GuiConst_INT16S textY1 = 220;
    const GuiConst_INT16U textFont = GuiFont_Helv1;
    
    const GuiConst_INT16S boxX1 = 338;
    const GuiConst_INT16S boxY1 = 195;
    const GuiConst_INT16S boxX2 = 462;
    const GuiConst_INT16S boxY2 = 235;
    
    GuiLib_Box(boxX1, boxY1, boxX2, boxY2, buttonColor);
    
    GuiLib_DrawStr(
        textX1,                 //GuiConst_INT16S X,
        textY1,                 //GuiConst_INT16S Y,
        textFont,               //GuiConst_INT16U FontNo,
        buttonText,             //GuiConst_TEXT PrefixLocate *String,
        GuiLib_ALIGN_CENTER,    //GuiConst_INT8U Alignment, 
        GuiLib_PS_ON,           //GuiConst_INT8U PsWriting,
        GuiLib_TRANSPARENT_ON,  //GuiConst_INT8U Transparent,
        GuiLib_UNDERLINE_OFF,   //GuiConst_INT8U Underlining,
        0,                      //GuiConst_INT16S BackBoxSizeX,
        0,                      //GuiConst_INT16S BackBoxSizeY1,
        0,                      //GuiConst_INT16S BackBoxSizeY2,
        GuiLib_BBP_NONE,        //GuiConst_INT8U BackBorderPixels,
        buttonColor,            //GuiConst_INTCOLOR ForeColor,
        SixteenBitColorValue(0xFF, 0xFF, 0xFF)   //GuiConst_INTCOLOR BackColor (should be ignored with GuiLib_TRANSPARENT_ON)
    );
#endif // USE_BITMAPS
}


// Make these values available to the rest of the application
GuiConst_INTCOLOR GetFakeBackgroundBitmapMainColour(void)
{
    return SixteenBitColorValue(173, 222, 231); // From Background317 bitmap
    //return SixteenBitColorValue(0, 148, 165); // From Background320 bitmap
}

GuiConst_INTCOLOR GetFakeBackgroundBitmapButtonBandColour(void)
{
    //return SixteenBitColorValue(173, 173, 173); // Grey band at bottom
    return SixteenBitColorValue(192, 192, 192);
}


/*
    Draws the background bitmap. It fills the screen -
    so you do not need to call GuiLib_Clear if you call this.
    (*** BUT - maybe this causes easyGUI to lose track of which page is displayed,
    and/or of its touch areas - so reinstated as a test with 
    '#define WANT_GUILIB_CLEAR_BEFORE_DRAWING_BITMAP' ***)
    
    Alternatively - if the bitmap we are using has blocks of solid colour anyway - 
    draw the areas directly (we could then save memory by not including 
    the actual bitmap in the application)
*/
static void DrawSpecifiedBackgroundBitmap(GuiConst_INT8U bitmapIndex)
{
// Does this solve bug #4?
//#define WANT_GUILIB_CLEAR_BEFORE_DRAWING_BITMAP
#ifdef WANT_GUILIB_CLEAR_BEFORE_DRAWING_BITMAP
    GuiLib_Clear();
#undef WANT_GUILIB_CLEAR_BEFORE_DRAWING_BITMAP
#endif //WANT_GUILIB_CLEAR_BEFORE_DRAWING_BITMAP
// Answer - no, it doesn't

//#define FAKE_BITMAP
#ifdef FAKE_BITMAP
    GuiLib_FillBox(0, 0, 800, 423, GetFakeBackgroundBitmapMainColour());
    GuiLib_FillBox(0, 424, 800, 480, GetFakeBackgroundBitmapButtonBandColour());
#undef FAKE_BITMAP
#else
    GuiLib_ShowBitmap(bitmapIndex, 0, 0, -1); // -1 means 'no transparent colour'
#endif // FAKE_BITMAP
}

/*
    Draws the default background bitmap, i.e. *without* the Ellutia logo
*/
void DrawBackgroundBitmap(void)
{
    DrawSpecifiedBackgroundBitmap(GuiStruct_Bitmap_BlankBackground);
}

/*
    Same as the above, but draws the background bitmap *with* the Ellutia logo
*/
void DrawBackgroundBitmapWithLogo(void)
{
#ifdef USE_QSPI_BACKGROUND_BITMAPS
    qspiBitmaps.DisplayBootScreenBitmap();
#else
    DrawSpecifiedBackgroundBitmap(GuiStruct_Bitmap_BootScreen);
#endif // USE_QSPI_BACKGROUND_BITMAPS
}

/*
    For Display (LPC4088) Bug #11, draw a background bitmap without a grey bar at the bottom.
    For now, fake this with a page full of one colour
*/
void DrawFakeBackgroundBitmapForNumericKeypadPage(void)
{
//    GuiLib_FillBox(0, 0, 800, 480, SixteenBitColorValue(0, 90, 99));
// Above is for the old background bitmap. For the new "BlankBackground", we need...
    GuiLib_FillBox(0, 0, 800, 480, SixteenBitColorValue(255, 255, 255));
}


/*
    This functions erases the portions of the door Lock and Release buttons (on the Column pages)
    that remain visible when they are replaced by the Close or Unlock buttons.
    
    I have to admit that I still do not understand why simply redrawing the whole page 
    (starting with the background bitmap) does not do this.
    
    No arguments, no return code.
*/
void DrawBackgroundBitmapOverDoorLockAndReleaseButtons(void)
{
    // I also do not understand why the first coordinate pair in the call to GuiLib_ShowBitmapArea
    // needs to be 0, 0 to display the correct part of the bitmap. I expected them to be the same 
    // as the second coordinate pair - but that caused the top left portion of the bitmap to be displayed,
    // not the part where the buttons actually are.
    GuiLib_ShowBitmapArea(GuiStruct_Bitmap_BlankBackground, 0, 0, 248, 405, 323, 480, -1); // -1 means 'no transparent colour'
    GuiLib_ShowBitmapArea(GuiStruct_Bitmap_BlankBackground, 0, 0, 477, 405, 552, 480, -1); // -1 means 'no transparent colour'
//    GuiLib_FillBox(248, 405, 323, 480, SixteenBitColorValue(165, 165, 173));
//    GuiLib_FillBox(477, 405, 552, 480, SixteenBitColorValue(115, 123, 132));
}
#define USING_BACKGROUND_BITMAP



/*
    Copies the build date (#define'd as BUILD_DATE at the top of this file)
    to the easyGUI variable that displays it on the Settings page
*/
void SetupEasyGUIBuildDateVariable(void)
{
    strcpy(GuiVar_buildDate, BUILD_DATE);
}


/*
    Draw a part of a profile - which will be a quadrilateral, vertical at left and right, horizontal at the bottom, but a sloping straight line at the top -
    by calling the EasyGUI GuiLib_VLine function multiple times.
    
    Args: colour of the profile to the left of the boundary
          colour of the profile to the right of the boundary
          X coords of the left and right edges of the section
          X coord of the colour boundary
          Y coord of the bottom
          Y coords of the top left and top right
          
    Returns true if OK, false if it failed (e.g. the coords were invalid).
*/
bool DrawProfileSectionUsingGuiLibVLine(GuiConst_INTCOLOR colour1, GuiConst_INTCOLOR colour2, GuiConst_INT16S xLeft, GuiConst_INT16S xRight, GuiConst_INT16S xColourBoundary, 
                                        GuiConst_INT16S yBottom, GuiConst_INT16S yTopLeft, GuiConst_INT16S yTopRight) 
{
    if(xRight <= xLeft) return false; // Would cause divide by zero if they were equal

    // y = mx + c
    double yDiff = (double) (yTopRight - yTopLeft);
    double xDiff = (double) (xRight - xLeft);
    double m = yDiff / xDiff;
    double c = (double) yTopLeft - (m * (double) xLeft);
    
    GuiConst_INT16S lineX;
    double lineYTop;
    
    // Draw first colour up to boundary.
    // Allow for the boundary being past the right-hand end
    for (lineX = xLeft; (lineX <= xColourBoundary) && (lineX <= xRight); ++lineX) {
        lineYTop = (((double) lineX) * m) + c;
        GuiLib_VLine(lineX, yBottom, (GuiConst_INT16S) lineYTop, colour1);
    }
    
    // Draw second colour after boundary, if we have not already reached the right-hand end
    for (; lineX <= xRight; ++lineX) {
        lineYTop = (((double) lineX) * m) + c;
        GuiLib_VLine(lineX, yBottom, (GuiConst_INT16S) lineYTop, colour2);
    }
    
    return true;
}

/*
    Displays the specified easyGUI 'structure' (or page, to use a more easily understood term).
    
    Args are: the index of the structure to be displayed,
              a pointer to the USBHostGC instance that corresponds to the GC,
              a pointer to the USBDeviceConnected instance that (also) corresponds to the GC
              a boolean - defaulting to true - that gives the caller the option to stop the 'structure'
                updating its easyGUI variables (either from the GC or from its own internal values) 
                when it is redisplayed. This is set false by the NumericKeypadHandler, 
                when the user has just pressed the Apply button on the Numeric Keypad page 
                and NumericKeypadHandler has updated the corresponding easyGUI variable 
                on the page that called it, to stop the calling page/structure 
                from promptly overwriting that same variable.
              
    No return code.
    
    TODO: "Refactor" this function. It has had code added to it piecemeal over a long period,
          and has become too large to easily understand.
*/
void DisplayEasyGuiStructure(int structureIndex, USBDeviceConnected* usbDevice, USBHostGC* usbHostGC, bool updateEasyGUIVariables = true)
{
    // If required, query the GC to find out if it is ready to run, etc, *before* clearing the display -
    // otherwise the display remains clear while we talk to the GC - causes noticeable flickering
    GCStateSimplified simplifiedGCState = GC_IDLE;
    if((structureIndex == GuiStruct_HomePage_1) && (usbDevice != NULL) && (usbHostGC != NULL)) {
        int gcStatus = GetGCStatus(usbDevice, usbHostGC);
        simplifiedGCState = GCStateOrFaultCode::GetSimplifiedGCState(gcStatus);
    }
    
#define DEBUG_HERE
#ifdef DEBUG_HERE
    char dbg[100];
    sprintf(dbg, "DisplayEasyGuiStructure - structure is %d", structureIndex);
    EasyGUIDebugPrint(dbg, 0, 20);
#undef DEBUG_HERE
#endif

#ifdef USING_BACKGROUND_BITMAP
    // We want the status rectangles to be 'on top' of this - 
    // if we include it in the easyGUI page itself, it gets drawn by GuiLib_ShowScreen,
    // and overwrites the rectangles
    if((structureIndex == GuiStruct_GCConnectionPage_Def) || (structureIndex == GuiStruct_EthernetConnectionPage_Def)) {
        DrawBackgroundBitmapWithLogo(); 
    } else if ((structureIndex == GuiStruct_NumericKeypadPage_Def) || (structureIndex == GuiStruct_EthernetKeypadPage_Def)) {
        DrawFakeBackgroundBitmapForNumericKeypadPage();
    } else {
        DrawBackgroundBitmap(); 
    }
#else
    GuiLib_Clear(); // Don't need this if we have drawn the background bitmap - it covers the entire screen
#endif

    // Now, display the status rectangles and the component bitmaps, as appropriate for the page we are displaying.
    // Note - we draw the status rectangles after GuiLib_Clear - otherwise we wouldn't see the rectangles at all - 
    // and before GuiLib_ShowScreen - so text, etc, is drawn on top of the rectangles.
    // We draw the component bitmaps, after - therefore, on top of - the status rectangles.
    switch(structureIndex) {
        case GuiStruct_HomePage_1:
#ifdef USE_QSPI_BITMAPS
            qspiBitmaps.DisplayAllHomePageBitmaps();
#else
            homePageGCComponentStatusColorAreas.DisplayAll();
#endif // USE_QSPI_BITMAPS
            break;
            
        case GuiStruct_ColumnPage1_2:
        case GuiStruct_ColumnPage2_9:
        case GuiStruct_ColumnMethodPage_Def:
#ifdef WANT_COMPONENT_ICON_ON_PROFILE_PAGES
        case GuiStruct_ColumnTempProfilePage_60:
#endif // WANT_COMPONENT_ICON_ON_PROFILE_PAGES
            // Column status rectangle no longer used
            //singleGCComponentPageStatusColorAreas.DisplayGCComponentStatus(COLUMN);
            qspiBitmaps.DisplayColumnComponentBitmap();
            break;
            
        case GuiStruct_InjectorPage1_3:
        case GuiStruct_InjectorMethodPage_Def:
#ifdef WANT_COMPONENT_ICON_ON_PROFILE_PAGES
        case GuiStruct_InjectorTempProfilePage_25:
#endif // WANT_COMPONENT_ICON_ON_PROFILE_PAGES
            // Injector status rectangle no longer used
            //singleGCComponentPageStatusColorAreas.DisplayGCComponentStatus(INJECTOR);
            qspiBitmaps.DisplayInjectorComponentBitmap();
            break;
            
        case GuiStruct_DetectorFIDPage_4:
        case GuiStruct_DetectorECDPage_12:
        case GuiStruct_DetectorFPDPage_14:
        case GuiStruct_DetectorTCDPage_11:
        case GuiStruct_DetectorNonePage_31:
        case GuiStruct_DetectorNPDPage_28:
        case GuiStruct_DetectorPIDPage_29:
        case GuiStruct_DetectorSPDIDPage_30:
        case GuiStruct_DetectorTXLPage_27:
            // Detector status rectangle no longer used
            //singleGCComponentPageStatusColorAreas.DisplayGCComponentStatus(DETECTOR);
            qspiBitmaps.DisplayDetectorComponentBitmap();
            break;
            
#ifdef WANT_COMPONENT_ICON_ON_PROFILE_PAGES
        case GuiStruct_GasProfilePage_15:
#endif // WANT_COMPONENT_ICON_ON_PROFILE_PAGES
        case GuiStruct_GasMethodPage_Def:
        case GuiStruct_GasInformationPage_6:
#ifdef WANT_STATUS_RECTANGLE_ON_GAS_CALIB_PAGES        
        case GuiStruct_GasCalibrationPage_Def:
        case GuiStruct_GasBackPressureDACPage_Def:
        case GuiStruct_GasChannelDACAndADCPage_Def:
#endif
            // Gas status rectangle no longer used
            //singleGCComponentPageStatusColorAreas.DisplayGCComponentStatus(GAS);
            qspiBitmaps.DisplayGasComponentBitmap();
            break;
            
        default: // Don't need to display status rectangle for this page
            break;
    }
    
    if(structureIndex == GuiStruct_RunningPage1_7) {
        if(getGCStatusLoop != NULL) {
            getGCStatusLoop->UpdateMethodRunTimeEasyGUIVariables(false);
        }
    }
    
    if(structureIndex == GuiStruct_ColumnPage1_2){
        if(getGCStatusLoop != NULL) {
            getGCStatusLoop->UpdateColumnStatusEasyGUIVariable();
        }    
    }
    
    if(structureIndex == GuiStruct_InjectorPage1_3){
        if(getGCStatusLoop != NULL) {
            getGCStatusLoop->UpdateInjectorStatusEasyGUIVariable();
        }    
    }
    
    if(structureIndex == GuiStruct_EthernetParametersPage_50) {
        NetworkParameters *networkParameters = NetworkParameters::GetInstance(usbDevice, usbHostGC);
        
        if(networkParameters != NULL) {
            networkParameters->DisplayingEasyGUIPage(updateEasyGUIVariables);
        }
    }

    if(structureIndex == GuiStruct_GasCalibrationPage_Def) {
        GasCalibrationPageHandler *gasCalibrationPageHandler = GasCalibrationPageHandler::GetInstance(usbDevice, usbHostGC);
        
        if(gasCalibrationPageHandler != NULL) {
            gasCalibrationPageHandler->DisplayingEasyGUIPage(updateEasyGUIVariables);
        }
    }

    if(structureIndex == GuiStruct_GasBackPressureDACPage_Def) {
        GasBackPressureDACPageHandler *gasBackPressureDACPageHandler = GasBackPressureDACPageHandler::GetInstance(usbDevice, usbHostGC);
        
        if(gasBackPressureDACPageHandler != NULL) {
            gasBackPressureDACPageHandler->DisplayingEasyGUIPage(updateEasyGUIVariables);
        }
    }

    if(structureIndex == GuiStruct_GasChannelDACAndADCPage_Def) {
        GasChannelDACAndADCPageHandler *gasChannelDACAndADCPageHandler = GasChannelDACAndADCPageHandler::GetInstance(usbDevice, usbHostGC);
        
        if(gasChannelDACAndADCPageHandler != NULL) {
            gasChannelDACAndADCPageHandler->DisplayingEasyGUIPage();
        }
    }

    if(structureIndex == GuiStruct_ColumnDHAutoCalibrationPage_Def) {
        ColumnDHAutoCalibrationPageHandler *columnDHAutoCalibrationPageHandler = ColumnDHAutoCalibrationPageHandler::GetInstance(usbDevice, usbHostGC);
        
        if(columnDHAutoCalibrationPageHandler != NULL) {
            columnDHAutoCalibrationPageHandler->DisplayingEasyGUIPage();
        }
    }
    
    if(structureIndex == GuiStruct_ColumnDHManualCalibrationPage_Def) {
        ColumnDHManualCalibrationPageHandler *columnDHManualCalibrationPageHandler = ColumnDHManualCalibrationPageHandler::GetInstance(usbDevice, usbHostGC);
        
        if(columnDHManualCalibrationPageHandler != NULL) {
            columnDHManualCalibrationPageHandler->DisplayingEasyGUIPage(updateEasyGUIVariables);
        }
    }
    
    if(structureIndex == GuiStruct_ColumnDHSensorCalibration_Def) {
        ColumnDHSensorCalibrationPageHandler *columnDHSensorCalibrationPageHandler = ColumnDHSensorCalibrationPageHandler::GetInstance(usbDevice, usbHostGC);
        
        if(columnDHSensorCalibrationPageHandler != NULL) {
            columnDHSensorCalibrationPageHandler->DisplayingEasyGUIPage(updateEasyGUIVariables);
        }
    }

    if(structureIndex == GuiStruct_PSU_DAC_Page_Def) {
        ColumnDHPSUDACPageHandler *columnDHPSUDACPageHandler = ColumnDHPSUDACPageHandler::GetInstance(usbDevice, usbHostGC);
        
        if(columnDHPSUDACPageHandler != NULL) {
            columnDHPSUDACPageHandler->DisplayingEasyGUIPage(updateEasyGUIVariables);
        }
    }
    
    if(NudgeAndDampPageHandler::PageIsANudgeAndDampPage(structureIndex)) {
        NudgeAndDampPageHandler *nudgeAndDampPageHandler = NudgeAndDampPageHandler::GetInstance(usbDevice, usbHostGC);
        
        if(nudgeAndDampPageHandler != NULL) {
            nudgeAndDampPageHandler->DisplayingEasyGUIPage(structureIndex, updateEasyGUIVariables);
        }
    }
    
    
    if(structureIndex == GuiStruct_ServicingHomePage_Def) {
#ifdef USE_QSPI_BITMAPS
        // Use same bitmaps on Servicing Home Page, as on the main Home Page
        qspiBitmaps.DisplayAllHomePageBitmaps();
#endif
    }

    GuiLib_ShowScreen(structureIndex, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);
    
    // But draw the run button, if required, on top of the fixed part of the home page
    // Same for the Heat On/Off button, and the Stabilising or Equilibrating messages (again, if required)
    if(structureIndex == GuiStruct_HomePage_1) {
//#define ALWAYS_WANT_RUN_BUTTON
#ifdef ALWAYS_WANT_RUN_BUTTON
        DrawRunButton((simplifiedGCState == GC_READY_TO_RUN) || (simplifiedGCState == GC_RUNNING));
#else
        if((simplifiedGCState == GC_READY_TO_RUN) || (simplifiedGCState == GC_RUNNING)) {
            DrawRunButton(true);
        }
#endif // ALWAYS_WANT_RUN_BUTTON
        
#ifdef USE_HEAT_ONOFF_BUTTON_BITMAPS
        DrawHeatOnOffButton();
#endif

        GCStateOrFaultCode::DrawSimplifiedStateMessageOnHomePageRunButton(simplifiedGCState);
    }
    
    if(structureIndex == GuiStruct_InjectorTempProfilePage_25) {

        // Make sure the injector temperature profile graph is drawn 
        // on entering this page (note that we must do this *after* 
        // drawing the injector status rectangle and *after* 'GuiLib_ShowScreen')

        if(getGCStatusLoop != NULL) {
            getGCStatusLoop->DisplayInjectorTempProfilePageGraph();
        }
    }    
            
    if(structureIndex == GuiStruct_GasProfilePage_15) {

        // Make sure the gas pressure profile graph is drawn 
        // on entering this page (note that we must do this *after* 
        // drawing the gas status rectangle and *after* 'GuiLib_ShowScreen')

        if(getGCStatusLoop != NULL) {
            getGCStatusLoop->DisplayGasFlowProfilePageGraph();
        }
    }    
            
    if(structureIndex == GuiStruct_ColumnTempProfilePage_60) {

        // Make sure the column temperature profile graph is drawn 
        // on entering this page (note that we must do this *after* 
        // drawing the column status rectangle and *after* 'GuiLib_ShowScreen')

        if(getGCStatusLoop != NULL) {
            getGCStatusLoop->DisplayColumnTempProfilePageGraph(CONVENTIONAL_COLUMN);
        }

//Also in GetGCStatusLoop::DisplayColumnTempProfilePageData
//#define SWIM_TEST
#ifdef SWIM_TEST
        SwimDraw* swimDrawInstance = SwimDraw::GetInstance();
        if(swimDrawInstance != NULL) {
            // two horizontal boxes
            swimDrawInstance->DrawRectangle(SixteenBitColorValue(0xFF, 0, 0), 50, 200, 650, 250);
            swimDrawInstance->DrawRectangle(SixteenBitColorValue(0, 0, 0xFF), 50, 350, 650, 400);
            
            // two vertical boxes
            swimDrawInstance->DrawRectangle(SixteenBitColorValue(0xFF, 0, 0), 100, 50, 150, 350);
            swimDrawInstance->DrawRectangle(SixteenBitColorValue(0, 0, 0xFF), 500, 50, 550, 350);
        }
#else // Draw the same boxes with easyGUI
//        GuiLib_FillBox(50, 200, 650, 250, SixteenBitColorValue(0xFF, 0, 0));
//        GuiLib_FillBox(50, 350, 650, 400, SixteenBitColorValue(0, 0, 0xFF));
        
//        GuiLib_FillBox(100, 50, 150, 350, SixteenBitColorValue(0xFF, 0, 0));
//        GuiLib_FillBox(500, 50, 550, 350, SixteenBitColorValue(0, 0, 0xFF));

// No - draw a dummy profile section
//         DrawProfileSectionUsingGuiLibVLine(SixteenBitColorValue(0xFF, 0, 0), 300, 500, 350, 200, 125);
#endif // SWIM_TEST
    }    
    
    // Column page - must draw Lock/Release buttons if required
#ifdef WANT_DOOR_ACTUATOR_BUTTONS_ON_COLUMN_PAGES
    if((structureIndex == GuiStruct_ColumnDHAutoCalibrationPage_Def) ||
       (structureIndex == GuiStruct_ColumnPage1_2) ||
       (structureIndex == GuiStruct_ColumnPage2_9) ||
       (structureIndex == GuiStruct_ColumnMethodPage_Def) ||
       (structureIndex == GuiStruct_ColumnTempProfilePage_60)) {
#else
    if(structureIndex == GuiStruct_ColumnDHAutoCalibrationPage_Def) {
#endif
        SetupDoorActuatorCommandUserInterface(usbDevice, usbHostGC, false);
    }  

    if(structureIndex == GuiStruct_ColumnMethodPage_Def) {
        if(getGCStatusLoop != NULL) {
            getGCStatusLoop->ShowColumnMethodPageScrollButtonsIfNecessary();
        }
    }
        
    if(structureIndex == GuiStruct_InjectorMethodPage_Def) {
        if(getGCStatusLoop != NULL) {
            getGCStatusLoop->ShowInjectorMethodPageScrollButtonsIfNecessary();
        }
    }
        
    if(structureIndex == GuiStruct_GasMethodPage_Def) {
        if(getGCStatusLoop != NULL) {
            getGCStatusLoop->ShowGasMethodPageScrollButtonsIfNecessary();
        }
    }
        
    if(structureIndex == GuiStruct_RunningPage1_7) {
        if(getGCStatusLoop != NULL) {
            getGCStatusLoop->UpdateAndDisplayRunningPage1ProgressBar(false);
        }
    }
                        
    GuiLib_Refresh();
#define DEBUG_HERE
#ifdef DEBUG_HERE
    char dbg2[100];
    sprintf(dbg2, "After GuiLib_Refresh main 1");
    EasyGUIDebugPrint(dbg2, 0, 40);
#undef DEBUG_HERE
#endif
    
    if(getGCStatusLoop != NULL) {
        getGCStatusLoop->SetCurrentPage(structureIndex);
    }
} // End of "DisplayEasyGUIStructure"

/*
    Draws the specified error message in a standard position (X = 90, Y = 240),
    blue text on a green background.
    
    The argument is a pointer to the (null-terminated) message text.
*/
void DrawErrorMessage(char *msg)
{
    const GuiConst_INT16U fontNo = GuiFont_Helv1;
    
    GuiLib_DrawStr(
        90,                     //GuiConst_INT16S X,
        240,                    //GuiConst_INT16S Y,
        fontNo,                 //GuiConst_INT16U FontNo,
        msg,                    //GuiConst_TEXT PrefixLocate *String,
        GuiLib_ALIGN_LEFT,      //GuiConst_INT8U Alignment, 
        GuiLib_PS_ON,           //GuiConst_INT8U PsWriting,
        GuiLib_TRANSPARENT_OFF, //GuiConst_INT8U Transparent,
        GuiLib_UNDERLINE_OFF,   //GuiConst_INT8U Underlining,
        0,                      //GuiConst_INT16S BackBoxSizeX,
        0,                      //GuiConst_INT16S BackBoxSizeY1,
        0,                      //GuiConst_INT16S BackBoxSizeY2,
        GuiLib_BBP_NONE,        //GuiConst_INT8U BackBorderPixels,
        SixteenBitColorValue(0, 0, 0xFF),  //GuiConst_INTCOLOR ForeColor,
        SixteenBitColorValue(0, 0xFF, 0)   //GuiConst_INTCOLOR BackColor
    ); 
}

/*
    Update the easyGUI variable that displays the command to turn the GC heat on or off,
    so that it matches the GC's current state.
*/
void UpdateHeatOnOffEasyGuiVariable(void)
{
    // Note that the easyGUI variable is not the current status of the heat on the GC,
    // but the command to toggle its current state
    if(theGCHeatControl != NULL) {
        if(theGCHeatControl->IsHeatOn()) {
            strcpy(GuiVar_heatOnOffCommand, "Heat Off");
        } else {
            strcpy(GuiVar_heatOnOffCommand, "Heat On");
        }
    }
}

void DrawHeatOnOffButton(void)
{
    // Note that the text on the button is not the current status of the heat on the GC,
    // but the command to toggle its current state
    GuiConst_INT8U bitmapIndex = GuiStruct_Bitmap_HeatOn;
    if(theGCHeatControl != NULL) {
        if(theGCHeatControl->IsHeatOn()) {
            bitmapIndex = GuiStruct_Bitmap_HeatOff;
        }
    }
    
#define DEBUG_HERE
#ifdef DEBUG_HERE
    char dbg[100];
    sprintf(dbg, "DHOOB - bitmapIndex is %d", bitmapIndex);
    EasyGUIDebugPrint(dbg, 0, 440);    
#endif
#undef DEBUG_HERE

    // Hard coded coordinates copied from easyGUI (I have not found 
    // a way of getting them from the easyGUI code at runtime)
    GuiLib_ShowBitmap(bitmapIndex, 0, 404, -1); // No transparent colour
}


/*
    General function to pass a command to the GC, and return success or failure to the caller.
    
    Args are: the command (null-terminated string)
              a pointer to the USBHostGC instance that corresponds to the GC,
              a pointer to the USBDeviceConnected instance that (also) corresponds to the GC
              
    Returns true if the GC returned "DACK" in response, false if it returned "DNAK" 
    (or "EPKT", or anything other than "DACK").
*/
bool ExecuteGCCommand(char *cmd, USBDeviceConnected* usbDevice, USBHostGC* usbHostGC)
{
#define USE_GC_UTILS // Testing new class
#ifdef USE_GC_UTILS
    return USBHostGCUtilities::SendCommandToGCWithDACKResponse(usbDevice, usbHostGC, cmd);
#else
    while(usbHostGC->ExecutingSetDeviceReport()) {}

    char response[50];
    usbHostGC->SetDeviceReport(usbDevice, cmd, response);
    // We expect a response like this: "DACK" for success, "DNAK" for failure
    
#define DEBUG_HERE
#ifdef DEBUG_HERE
    char dbg[100];
    sprintf(dbg, "%s returned %s", cmd, response);
    EasyGUIDebugPrint(dbg, 0, 20);    
#endif
#undef DEBUG_HERE

    return (response[1] == 'A');
#endif // USE_GC_UTILS 
}

/*
    Starts the GC running, by passing it the "CRUN" command.
    
    Args are: a pointer to the USBHostGC instance that corresponds to the GC,
              a pointer to the USBDeviceConnected instance that (also) corresponds to the GC
              
    Returns true if the GC returned "DACK" in response, false if it returned "DNAK".
*/
bool StartGCRun(USBDeviceConnected* usbDevice, USBHostGC* usbHostGC)
{
    return ExecuteGCCommand("CRUN", usbDevice, usbHostGC);
}

/*
    Stops the GC running, by passing it the "CSTP" or "CABT" command.
    
    Args are: a pointer to the USBHostGC instance that corresponds to the GC,
              a pointer to the USBDeviceConnected instance that (also) corresponds to the GC
              
    Returns true if the GC returned "DACK" in response, false if it returned "DNAK".
*/
bool StopGCRun(USBDeviceConnected* usbDevice, USBHostGC* usbHostGC)
{
// TODO: Find out which is the correct command here
//    char *cmd = "CSTP";
    char *cmd = "CABT";
    return ExecuteGCCommand(cmd, usbDevice, usbHostGC);
}

/*
    Takes the GC out of standby mode, by passing it the "CDIS" command.
    
    Args are: a pointer to the USBHostGC instance that corresponds to the GC,
              a pointer to the USBDeviceConnected instance that (also) corresponds to the GC
              
    Returns true if the GC returned "DACK" in response, false if it returned "DNAK".
*/
bool ExitGCStandbyMode(USBDeviceConnected* usbDevice, USBHostGC* usbHostGC)
{
    if( ExecuteGCCommand("CDIS", usbDevice, usbHostGC)) {
        if(getGCStatusLoop != NULL) {
            getGCStatusLoop->ExitedGCStandbyMode();
        }

        return true;
    }
    
    // 'else'
    return false;    
}

/*
    Takes the GC out of its error state, by passing it the "CCLR" command.
    
    Args are: a pointer to the USBHostGC instance that corresponds to the GC,
              a pointer to the USBDeviceConnected instance that (also) corresponds to the GC
              
    Returns true if the GC returned "DACK" in response, false if it returned "DNAK".
*/
bool ClearGCErrors(USBDeviceConnected* usbDevice, USBHostGC* usbHostGC)
{
    return ExecuteGCCommand("CCLR", usbDevice, usbHostGC);
}


/*
    Opens the (column) door on the GC, by passing it the "COPN" command.
    
    Args are: a pointer to the USBHostGC instance that corresponds to the GC,
              a pointer to the USBDeviceConnected instance that (also) corresponds to the GC
              
    Returns true if the GC returned "DACK" in response, false if it returned "DNAK".
*/
bool OpenDoor(USBDeviceConnected* usbDevice, USBHostGC* usbHostGC)
{
    return ExecuteGCCommand("COPN", usbDevice, usbHostGC);
}


/*
    Set up the variable "GuiVar_doorActuatorCommandFGColour", to show - depending on
    the current state of the GC - whether the Lock/Unlock facility is enabled or not,
    by displaying the command in black (enabled) or grey (disabled).

    Args are: a pointer to the USBHostGC instance that corresponds to the GC,
              a pointer to the USBDeviceConnected instance that (also) corresponds to the GC
              
    No return code.
*/
void SetupDoorActuatorCommandColour(USBDeviceConnected* usbDevice, USBHostGC* usbHostGC, bool actuatorsAreMoving)
{
    // We allow the user to change the state of the actuators only if the GC is in the idle or fault states,
    // and if there is no other reason why they should be disabled (e.g. we are calibrating, and the oven is hot 
    // even though the heat is not on. Show this to the user by setting the text colour to black (enabled)
    // or grey (disabled)
    bool enabled = false;
    if(!actuatorsAreMoving) {
        GCStateSimplified simplifiedGCState = GCStateOrFaultCode::GetSimplifiedGCState(GetGCStatus(usbDevice, usbHostGC));
        if((simplifiedGCState == GC_IDLE) || (simplifiedGCState == GC_FAULTED)) {
            if(canUnlockDoorActuators) {
                enabled = true;
            }
        }
    }
    // All other situations - must be disabled

    if(enabled) {
        GuiVar_doorActuatorCommandFGColour = 0; // Black
    } else {
        GuiVar_doorActuatorCommandFGColour = SixteenBitColorValue(192, 192, 192); // Grey - but bright enough to show up against grey button colour
    }
}


typedef enum enumDoorActuatorStatus { LOCKED, UNLOCKED, CLOSED_BUT_NOT_LOCKED, INTERMEDIATE, MOVING } DoorActuatorStatus;
static bool lockingActuators; // Do not display "Release" while we are doing this
/*
    Finds out, and returns, the status of the door actuators.    

    Args are: a pointer to the USBHostGC instance that corresponds to the GC,
              a pointer to the USBDeviceConnected instance that (also) corresponds to the GC
              
    Returns the status, as a value in the 'DoorActuatorStatus' enumeration defined above.
*/
DoorActuatorStatus GetDoorActuatorStatus(USBDeviceConnected* usbDevice, USBHostGC* usbHostGC)
{
    while(usbHostGC->ExecutingSetDeviceReport()) {}

    char response[50];
    usbHostGC->SetDeviceReport(usbDevice, "QACT1004", response);
    
    
    // We expect a response of the form "DACnnnnn", where "nnnnn" is the decimal value
    // of two bytes, the most significant of which contains the states of each of the three actuators,
    // while the least significant contains the software version (which we ignore).
    
    int actuatorStatus;
    sscanf(&response[3], "%d", &actuatorStatus);
    
    if(actuatorStatus & 0x8000) { // Top bit set
        return MOVING;
    }
    
    int actuator1Status = (actuatorStatus & 0x300) >> 8;
    int actuator2Status = (actuatorStatus & 0xC00) >> 10;
    int actuator3Status = (actuatorStatus & 0x3000) >> 12;
    
    // Status 1 means locked, 2 means unlocked, 3 means closed but not locked (actuators 1 and 2 only),
    // 0 means intermediate 
    if ((actuator1Status == 1) && (actuator2Status == 1) && (actuator3Status == 1)) {
        return LOCKED;
    }
    // 'else' ...
    if((actuator1Status == 2) && (actuator2Status == 2) && (actuator3Status == 2)) {
        return UNLOCKED;
    }
    // 'else' ...
    if((actuator1Status == 3) && (actuator2Status == 3) && (actuator3Status == 2)) {
        return CLOSED_BUT_NOT_LOCKED;
    }
    // 'else' ...
    return INTERMEDIATE;
}

/*
    We have now replaced the "Open Door" button with a button to toggle 
    the state of the door actuators - i.e. lock them if they are unlocked 
    and vice versa. We display the corresponding command in the easyGUI variable 
    "GuiVar_doorActuatorCommand". This function reads the current state 
    of the actuators, and sets up that variable appropriately. It also sets up 
    the variable "GuiVar_doorActuatorCommandFGColour", to show - depending on
    the current state of the GC - whether the facility is enabled or not,
    by displaying the command in black (enabled) or grey (disabled).
    
    It also now - since we have a separate state for "closed but not locked" - 
    displays the two "Lock" and "Release" buttons if required.

    Args are: a pointer to the USBHostGC instance that corresponds to the GC,
              a pointer to the USBDeviceConnected instance that (also) corresponds to the GC
              a boolean set to true before displaying the easyGUI page (GuiLib_ShowScreen), false if afterwards      

    No return code.
*/
void SetupDoorActuatorCommandUserInterface(USBDeviceConnected* usbDevice, USBHostGC* usbHostGC, bool beforePageDisplay)
{
//#define DEBUG_PRINT_HERE
#ifdef DEBUG_PRINT_HERE
    char buff[100];
    sprintf(buff, "%s", beforePageDisplay ? "Before page display" : "After page display");
    SpecialDebugPrint(buff, 150, 430);
#endif

    DoorActuatorStatus doorActuatorStatus = GetDoorActuatorStatus(usbDevice, usbHostGC);
    
//    SetupDoorActuatorCommandColour(usbDevice, usbHostGC, (doorActuatorStatus == MOVING));
    SetupDoorActuatorCommandColour(usbDevice, usbHostGC, false);

    if(beforePageDisplay) {
            
        // Do nothing - do not change the button text, etc - if the actuators are moving
        if(doorActuatorStatus != MOVING) {
            if(doorActuatorStatus == UNLOCKED) {
                strcpy(GuiVar_doorActuatorCommand, "Close");
            } else if (doorActuatorStatus == LOCKED) {
                strcpy(GuiVar_doorActuatorCommand, "Unlock");
                lockingActuators = false;
            } else { // i.e. all other states, including INTERMEDIATE (possible emergency stop)
                if(!lockingActuators) { // Do not display this while we are locking - confusing
                    strcpy(GuiVar_doorActuatorCommand, "Release");
                }
            } 
        }
    } else {
        if(doorActuatorStatus == CLOSED_BUT_NOT_LOCKED) {
            GetGCStatusLoop::DisplayColumnLockAndReleaseButtons();
#ifdef DEBUG_PRINT_HERE
            char buff2[100];
            sprintf(buff2, "%s", "Drawing lock and release buttons");
            SpecialDebugPrint(buff2, 150, 460);
#endif
        } else { // Including MOVING
            DrawBackgroundBitmapOverDoorLockAndReleaseButtons();
#ifdef DEBUG_PRINT_HERE
            char buff2[100];
            sprintf(buff2, "%s", "*** NOT *** drawing lock and release buttons");
            SpecialDebugPrint(buff2, 150, 460);
#undef DEBUG_PRINT_HERE
#endif
        }
    }
}


/*
    Tells the caller whether or not the door actuator buttons have changed,
    from the single "Close"/"Unlock" button to the two "Lock" and "Release" buttons,
    or vice versa
*/
bool DoorActuatorButtonsHaveChanged(USBDeviceConnected* usbDevice, USBHostGC* usbHostGC)
{
    bool doorActuatorButtonsHaveChanged = false;
    
    static DoorActuatorStatus previousDoorActuatorStatus = INTERMEDIATE;
    
    DoorActuatorStatus doorActuatorStatus = GetDoorActuatorStatus(usbDevice, usbHostGC);
    
    // Do not change buttons if actuators are moving
    if(doorActuatorStatus != MOVING) {
        if((doorActuatorStatus == LOCKED) || (doorActuatorStatus == UNLOCKED)) {
            if((previousDoorActuatorStatus == CLOSED_BUT_NOT_LOCKED) || (previousDoorActuatorStatus == INTERMEDIATE)) {
                doorActuatorButtonsHaveChanged = true;
            }
        } else if (doorActuatorStatus == CLOSED_BUT_NOT_LOCKED) {
            if(previousDoorActuatorStatus != CLOSED_BUT_NOT_LOCKED) {
                doorActuatorButtonsHaveChanged = true;
            }
        }
    
        // We ignore the 'moving' state
        previousDoorActuatorStatus = doorActuatorStatus;
    }
    
    return doorActuatorButtonsHaveChanged;
}


/*
    Toggles the state of the door actuator on the GC - i.e. if it is locked, 
    unlocks it, and vice versa. 
    
    Args are: the index of the touch area the user touched (note that we assume 
                this must be one of the 'door actuator areas' (seeEasyGUITouchAreaIndices.h)
              a pointer to the USBHostGC instance that corresponds to the GC,
              a pointer to the USBDeviceConnected instance that (also) corresponds to the GC
              
    Returns true if the GC executed the operation successfully, false if not
*/
bool DealWithDoorActuatorButtons(int touchAreaIndex, USBDeviceConnected* usbDevice, USBHostGC* usbHostGC)
{
    // Disallow movement unless GC is in idle or faulted states
    int gcStatus = GetGCStatus(usbDevice, usbHostGC);
    GCStateSimplified simplifiedGCState = GCStateOrFaultCode::GetSimplifiedGCState(gcStatus);
    if((simplifiedGCState != GC_IDLE) && (simplifiedGCState != GC_FAULTED)) {
        return false;
    }
    
    bool bOK = false;
    
    DoorActuatorStatus doorActuatorStatus = GetDoorActuatorStatus(usbDevice, usbHostGC);
    
    if(doorActuatorStatus != MOVING) {
        if(doorActuatorStatus == UNLOCKED) {
            // Two touch areas, but only one button - treat both the same
            // Close actuator, but do not lock it
            bOK = ExecuteGCCommand("CACT1005", usbDevice, usbHostGC);
        } else if (doorActuatorStatus == LOCKED) {
            // Two touch areas, but only one button - treat both the same
            // Unlock actuator
            bOK = ExecuteGCCommand("CACT1006", usbDevice, usbHostGC);
        } else if (doorActuatorStatus == CLOSED_BUT_NOT_LOCKED) {
            // Unlock or Lock, depending on the touch area
            if(touchAreaIndex == DOOR_ACTUATOR_AREA_1) {
                // Lock
                bOK = ExecuteGCCommand("CACT1007", usbDevice, usbHostGC);
                if(bOK) {
                    lockingActuators = true;
                    // Get the text ready for the Close/Unlock button
                    strcpy(GuiVar_doorActuatorCommand, "Unlock");
                }
            } else {
                // Assume DOOR_ACTUATOR_AREA_2 - release, i.e. unlock
                bOK = ExecuteGCCommand("CACT1006", usbDevice, usbHostGC);
                if(bOK) {
                    // Get the text ready for the Close/Unlock button
                    strcpy(GuiVar_doorActuatorCommand, "Close");
                }
            }
        } else { // Assume INTERMEDIATE - for safety, always unlock
            bOK = ExecuteGCCommand("CACT1006", usbDevice, usbHostGC);
        }
    }
    
    if(bOK) {
        SetupDoorActuatorCommandUserInterface(usbDevice, usbHostGC, false);
    }
    
    return bOK;
}


/*
    Tells the caller whether or not a specified GC command represents the start of a method,
    and that therefore a new method is now being sent to the GC.
    
    Params: pointer to a null-terminated string containing the command in question
    
    Returns true if the command is one that occurs at the start of a method (and nowhere else),
    false if not.
    
    This code is intended to be as efficient as possible.
    
    Currently, called from GetGCStatusLoop class and EthernetThread class
*/
bool IsStartOfMethodCommand(char *gcCommand)
{
    // We are looking for "CLCK" - lock local keyboard.
    // Ellution only sends this at the start of a method.
    
    if((gcCommand[0] == 'C') && (gcCommand[1] == 'L') && (gcCommand[2] == 'C') && (gcCommand[3] == 'K')) {
        return true;
    }
    
    return false; // Not "CLCK"
}

/*
    Tells the caller whether or not a specified GC command represents the end of a method,
    and that therefore a new method has just been sent to the GC.
    
    Params: pointer to a null-terminated string containing the command in question
    
    Returns true if the command is one that occurs at the end of a method (and nowhere else),
    false if not.
    
    This code is intended to be as efficient as possible.
    
    Currently, called from GetGCStatusLoop class and EthernetThread class
*/
bool IsEndOfMethodCommand(char *gcCommand)
{
    // We are looking for "CULK" - unlock local keyboard.
    // Ellution only sends this at the end of a method.
    
    if((gcCommand[0] == 'C') && (gcCommand[1] == 'U') && (gcCommand[2] == 'L') && (gcCommand[3] == 'K')) {
        return true;
    }
    
    return false; // Not "CULK"
}


/*
    Tells the caller whether or not a specified GC command is a control command,
    which received "DACK" in acknowledgement, and is therefore likely 
    to have caused the GC to change its state.
    
    Params: pointer to a null-terminated string containing the command in question
    
    Returns true if the command is a control command and if it succeeded, false if not.
    
    This code is intended to be as efficient as possible.
    
    Currently, called from GetGCStatusLoop class and EthernetThread class
*/
bool IsSuccessfulControlCommand(char* gcCommand, char* gcResponse)
{
    // Simple criteria - does the command start with 'C'?
    //                 - was the response "DACK"?
    
    if(gcCommand[0] != 'C') {
        return false;
    }

    
    if(gcResponse[0] != 'D') {
        return false;
    }

    if(gcResponse[1] != 'A') {
        return false;
    }

    if(gcResponse[2] != 'C') {
        return false;
    }

    if(gcResponse[3] != 'K') {
        return false;
    }

    // All the above were true
    return true;
}


/*
    Increment the run count recorded in the QSPI settings.
    
    This would be called (for example) after the Run button has been pressed.
*/
void IncrementRunCountInQSPISettings(void)
{
    int runCount = SettingsHandler::GetIntegerValueFromQSPISettings("RunCount", 0);
    
    // Debug prints
    char dbg[100];
    sprintf(dbg, "Run count read: %d", runCount);
    EasyGUIDebugPrint(dbg, 500, 460);
    
    SettingsHandler::DisplayQSPIDirectory(600, 250);
    // End of debug prints
    
    ++runCount;
    
    SettingsHandler::PutIntegerValueToQSPISettings("RunCount", runCount);
}

/*
    Update the values of all the easyGUI variables whose values are derived
    from the run count. This should be called (for example) after 
    the Run button has been pressed.
*/
void UpdateAllEasyGUIVariablesThatDependOnRunCount(void)
{
    int runCount = SettingsHandler::GetIntegerValueFromQSPISettings("RunCount", 0);

    int linerChangedRunCount = SettingsHandler::GetIntegerValueFromQSPISettings("RunCountWhenLinerChanged", 0);
    sprintf(GuiVar_injectionCountSinceLinerChanged, "%d", (runCount - linerChangedRunCount));

    int septaChangedRunCount = SettingsHandler::GetIntegerValueFromQSPISettings("RunCountWhenSeptaChanged", 0);
    sprintf(GuiVar_injectionCountSinceSeptaChanged, "%d", (runCount - septaChangedRunCount));

    int oRingChangedRunCount = SettingsHandler::GetIntegerValueFromQSPISettings("RunCountWhenOringChanged", 0);
    sprintf(GuiVar_injectionCountSinceOringChanged, "%d", (runCount - oRingChangedRunCount));
}


/*
    Sends the specified command to the GC.
    
    This must be a "Sxxx" command, that only expects "DACK" or "DNAK" back,
    not actual data.
    
    Args are: a pointer to the USBHostGC instance that corresponds to the GC,
              a pointer to the USBDeviceConnected instance that (also) corresponds to the GC
              
    Returns true if the GC returned "DACK" in response, false if it returned "DNAK".
*/
bool SendAnyCommandToGC(char *cmd, USBDeviceConnected* usbDevice, USBHostGC* usbHostGC)
{
#define USE_GC_UTILS // Testing new class
#ifdef USE_GC_UTILS
    return USBHostGCUtilities::SendCommandToGCWithDACKResponse(usbDevice, usbHostGC, cmd);
#else
    while(usbHostGC->ExecutingSetDeviceReport()) {}

    char response[50];
    usbHostGC->SetDeviceReport(usbDevice, cmd, response);
    // We expect a response like this: "DACK" for success, "DNAK" for failure
    
    return (response[1] == 'A');
#endif // USE_GC_UTILS
}


/*
    This function performs the "set up" operations required
    when the GC starts running. It is intended to be called 
    whenever the GC starts running - note that this may happen
    not only when the user presses our "Run" button, but if 
    he sends a "CRUN" command to the GC from the PC (e.g. using 
    Ellution), or if something triggers the "Run" pin 
    on the GC board itself (and that does not pass through
    the LPC4088, so it cannot be detected directly by this software).
    
    Args are: a pointer to the USBHostGC instance that corresponds to the GC,
              a pointer to the USBDeviceConnected instance that (also) corresponds to the GC

    No return code
*/
void SetupForStartOfRun(USBDeviceConnected* usbDevice, USBHostGC* usbHostGC)
{
    if(getGCStatusLoop != NULL) {
        getGCStatusLoop->SetGCIsRunning();
        IncrementRunCountInQSPISettings();
        UpdateAllEasyGUIVariablesThatDependOnRunCount();
        getGCStatusLoop->UpdateGCMethodRunningProfiles();
        getGCStatusLoop->SetRunningPage1ProgressBarToZero();
        getGCStatusLoop->SetupTemperatureWhileRunningEasyGUIVariables();
        DisplayEasyGuiStructure(GuiStruct_RunningPage1_7, usbDevice, usbHostGC);
    }
}


static void RemoveUnitsFromEasyGUIStringIfFound(char* destination, GuiConst_TEXT* easyGUIString, const char* units)
{
    strcpy(destination, easyGUIString);
    
    int unitsLength = strlen(units);
    int stringLength = strlen(destination);
    
    if(strcmp(units, &destination[stringLength - unitsLength]) == 0) {
        destination[stringLength - unitsLength] = '\0';
    }
}


/*
    This is the main function dealing with user interaction via the LPC4088 touch panel.
    ************************************************************************************
    
    It is (currently, until we rationalise this code) called by the GetGCStatusLoop code 
    that deals with touch events. (Ideally, we should move it into the GetGCStatusLoop class 
    at some point.)
    
    Args are: the touch coordinates (x, y and z - we do not use the z coordinate)
              a pointer to the USBHostGC instance that corresponds to the GC,
              a pointer to the USBDeviceConnected instance that (also) corresponds to the GC
              a count of timer ticks
              a boolean set to true if the touch is 'new', false if not (a touch is 'new' 
              if it occurs after a period when the user appears not to have touched the screen).
              
    Looks to see if the user has touched a defined easyGUI touch area, and responds accordingly.
              
    No return code.
    
    See EasyGUITouchAreaIndices.h for the touch area index values.
*/
void TouchCallback(touch_coordinate_t touchCoords, USBDeviceConnected* usbDevice, USBHostGC* usbHostGC, int tickCount, bool newTouch)
{
    EasyGUIDebugPrintWithCounter("TouchCallback 0   ", 500, 440);

    GuiConst_INT32S touchAreaIndex = GuiLib_TouchCheck((GuiConst_INT16S)touchCoords.x, (GuiConst_INT16S)touchCoords.y);

    EasyGUIDebugPrintWithCounter("TouchCallback 1   ", 500, 440);

    if(touchAreaIndex >= 0) {

//#define ALLOW_DEBUG_PRINTS_HERE
#ifdef ALLOW_DEBUG_PRINTS_HERE
        char buff[300];
        sprintf(buff, "tAI: %d", touchAreaIndex);
        SpecialDebugPrint(buff, 150, 410);
#undef ALLOW_DEBUG_PRINTS_HERE
#endif // ALLOW_DEBUG_PRINTS_HERE

        bool dealtWithTouch = false;
        
        // page selector?
        TouchPanelPageSelector* touchPanelPageSelector = touchPanelPageSelectors.GetTouchPanelPageSelector(touchAreaIndex);
        
        if( touchPanelPageSelector != NULL) {
            
            // User has touched a page selector
            // ********************************
            
            // Do not keep switching pages if the user keeps 'touching' - 
            // switch only if he 'lets go', then presses again
            if(newTouch) {

                // Depending on the touch area, we may need to do extra stuff here
                // before displaying the appropriate page
                
                if(touchAreaIndex == GAS_SAVER_RETURN_TO_READY) {
                    // Take GC out of standby mode
                    ExitGCStandbyMode(usbDevice, usbHostGC);
                }
                
                if(touchAreaIndex == CLEAR_ERRORS_BUTTON) {
                    // Take GC out of error state
                    ClearGCErrors(usbDevice, usbHostGC);
                    
                    // Make sure the component status rectangles change colour immediately,
                    // not after ~10 seconds
                    if(getGCStatusLoop != NULL) {
                        getGCStatusLoop->UpdateHomePageGCComponentStatusColorAreas();
                        
                        getGCStatusLoop->UpdateSingleGCComponentPageStatusColorArea(COLUMN);
                        getGCStatusLoop->UpdateSingleGCComponentPageStatusColorArea(INJECTOR);
                        getGCStatusLoop->UpdateSingleGCComponentPageStatusColorArea(DETECTOR);
                        getGCStatusLoop->UpdateSingleGCComponentPageStatusColorArea(GAS);
                    }
                }

                if(touchAreaIndex == ABORT_RUN_YES) {

                    // Abort Run request confirmed
                    
//#define DEBUG_PRINT_ON_FAILURE
#ifdef DEBUG_PRINT_ON_FAILURE
                    if(!StopGCRun(usbDevice, usbHostGC)) {
                        SpecialDebugPrint("*** StopGCRun failed ***", 150, 430);
                    }
#undef DEBUG_PRINT_ON_FAILURE
#else
                    StopGCRun(usbDevice, usbHostGC);
#endif // DEBUG_PRINT_ON_FAILURE
                    
                    getGCStatusLoop->ClearGCIsRunning();

#ifdef SERVICE_INTERVALS_ACTIVE
                    ServiceInterval::TellAllServiceIntervalsInstrumentHasCycled();
#endif // SERVICE_INTERVALS_ACTIVE

#ifdef TURN_HEAT_OFF_ON_ABORT
                    // Turn heat off
                    if(heatOnOffAvailable) {
                        if(theGCHeatControl != NULL) {
                            if(theGCHeatControl->IsHeatOn()) {
                                theGCHeatControl->TurnHeatOff();
#ifdef USE_HEAT_ONOFF_BUTTON_BITMAPS
                                DrawHeatOnOffButton();
#else
                                UpdateHeatOnOffEasyGuiVariable();
#endif // USE_HEAT_ONOFF_BUTTON_BITMAPS
                            }
                            
                            StartHeatOnOffTimeout();
                        }
                    }
#endif // TURN_HEAT_OFF_ON_ABORT
                }
                
                if(touchAreaIndex == SETTINGS_TO_NETWORK_PARAMS_BUTTON) {
                    // We are about to display the Network Parameters page - so tell the class 
                    // to display the real IP address, etc - i.e. the values we are actually using
                    NetworkParameters *networkParameters = NetworkParameters::GetInstance(usbDevice, usbHostGC);
                    if(networkParameters != NULL) {
                        networkParameters->RestoreRealValues();
                    }
                }
                
                if(touchAreaIndex == SETTINGS_TO_SERVICING_PAGE_BUTTON) {
                    strcpy(GuiVar_engineersLockCodeMessage, "No lock code entered");
                }
                
                if(touchAreaIndex == SERVICING_PAGE_GC_CMDS_BUTTON ) {
                    // Clear the easyGUI variables that display the command and the response
                    GuiVar_debugCommandTx[0] = '\0';
                    GuiVar_debugCommandRx[0] = '\0';
                }
                
                if(touchPanelPageSelector->CanChangePage()) {
                    DisplayEasyGuiStructure(touchPanelPageSelector->GetPageNumber(usbDevice, usbHostGC), usbDevice, usbHostGC);
                }
            }
                                   
            dealtWithTouch = true; // The user touched a page selector, so we have still 'dealt' with this, 
                                   // whether we had a 'new touch' or not
        } // TouchPanelPageSelector
        
        if(!dealtWithTouch) {
            
            if(touchAreaIndex == RUN_BUTTON) { 
                if(newTouch) { // As above - do not do this repeatedly - GC does not like it...
                
                    EasyGUIDebugPrintWithCounter("TouchCallback 4.1  ", 500, 440);
                    
                    if(GCIsReadyToRun(usbDevice, usbHostGC)) {
                        // Start run - if this works, display the 'Run' page, etc, else do nothing
                        if(StartGCRun(usbDevice, usbHostGC)) {
                            SetupForStartOfRun(usbDevice, usbHostGC);
                        } else {
                            DrawErrorMessage("*** Run failed to start ***");
                            
                            // Give user time to read error, then erase it
                            Thread::wait(2000);
                            
                            DisplayEasyGuiStructure(GuiStruct_HomePage_1, usbDevice, usbHostGC);
                        }                   
                    } else {
                        // GC is not ready to run - tell the user why 
                        getGCStatusLoop->SetupGCNotReadyStateEasyGUIVariable();
                            
                        DisplayEasyGuiStructure(GuiStruct_GCNotReadyToRunPage_Def, usbDevice, usbHostGC);
                    }
                }
            
                dealtWithTouch = true;
            }
        } // Run button

        if(!dealtWithTouch) {

            if(touchAreaIndex == HEAT_ON_BUTTON) { // Heat on/off button
                if(newTouch) { // As above - do not do this repeatedly - GC does not like it...
                    if(heatOnOffAvailable) {
                        if(theGCHeatControl != NULL) {
                            if(theGCHeatControl->IsHeatOn()) {
                                theGCHeatControl->TurnHeatOff();
                            } else {
                                theGCHeatControl->TurnHeatOn();
                            }
#ifdef USE_HEAT_ONOFF_BUTTON_BITMAPS
                            // If we do not do this first, we do not see the bitmap change on the button
                            // (I don't understand why...)
                            DisplayEasyGuiStructure(GuiStruct_HomePage_1, usbDevice, usbHostGC);
                            DrawHeatOnOffButton();
#else
                            UpdateHeatOnOffEasyGuiVariable();
                            // Make GuiVar_heatOnOffCommand update visible on screen
                            DisplayEasyGuiStructure(GuiStruct_HomePage_1, usbDevice, usbHostGC);
#endif // USE_HEAT_ONOFF_BUTTON_BITMAPS  
        
                            StartHeatOnOffTimeout();
                            
                            // Update the door lock/unlock command colour 
                            // *before* we display the column page(s)
                            SetupDoorActuatorCommandColour(usbDevice, usbHostGC, false);
                        }
                    }
                }
                                
                // Whether we changed the heat or not, the user still 'touched' our area,
                // so no-one else should try and deal with this touch
                dealtWithTouch = true;
            }
        } // Heat ON/Off button

        if(!dealtWithTouch) {

            if((touchAreaIndex == COLUMN_PAGE1_EDIT_COLUMN_MAX_TEMP) ||
               (touchAreaIndex == COLUMN_DH_PAGE1_EDIT_COLUMN_MAX_TEMP) ||
               (touchAreaIndex == COLUMN_PAGE2_EDIT_COLUMN_MAX_TEMP)) {

                NumericKeypadPageHandler* numericKeypadPageHandler = NumericKeypadPageHandler::GetInstance(usbDevice, usbHostGC);
                if(numericKeypadPageHandler != NULL) {
                    
                    // Remove units from the initial value to be displayed in the keypad
                    char temp[50];
                    RemoveUnitsFromEasyGUIStringIfFound(temp, GuiVar_columnMaxTemp2, GetGCStatusLoop::GetDegCUnitsWithSpace());
                    int easyGUICallingPage = GuiStruct_ColumnPage1_2;
                    if (touchAreaIndex == COLUMN_PAGE2_EDIT_COLUMN_MAX_TEMP) {
                        easyGUICallingPage = GuiStruct_ColumnPage2_9;
                    }
                    
                    numericKeypadPageHandler->StartEditing(temp);
                    numericKeypadPageHandler->SetEasyGUIVariableToEdit(GuiVar_columnMaxTemp2);
                    numericKeypadPageHandler->SetEasyGUICallingPage(easyGUICallingPage);
                    numericKeypadPageHandler->SetEditVariableRange(0, 500);
                    numericKeypadPageHandler->SetEditVariableName("Col. Max Temp");
                    numericKeypadPageHandler->SetEditVariableUnits(GetGCStatusLoop::GetDegCUnitsWithoutSpace());
                    numericKeypadPageHandler->SetApplyFunctionPtr(&SetColumnMaxTempFromEasyGuiVariable);
                    numericKeypadPageHandler->DisplayEasyGUIPage();
                }

                dealtWithTouch = true;
            }
        }

        if(!dealtWithTouch) {

            if(touchAreaIndex == ENTER_ENGINEERS_LOCK_CODE) {

                NumericKeypadPageHandler* numericKeypadPageHandler = NumericKeypadPageHandler::GetInstance(usbDevice, usbHostGC);
                if(numericKeypadPageHandler != NULL) {
                    numericKeypadPageHandler->StartEditingInLockMode(842, GuiStruct_ServicingHomePage_Def, GuiVar_engineersLockCodeMessage, "Invalid lock code");
                    numericKeypadPageHandler->SetEasyGUICallingPage(GuiStruct_EngineersLockPage_Def);
                    numericKeypadPageHandler->SetEditVariableRange(0, 32767);
                    numericKeypadPageHandler->SetEditVariableName("Lock Code");
                    numericKeypadPageHandler->DisplayEasyGUIPage();
                }

                dealtWithTouch = true;
            }
        }

        if(!dealtWithTouch) {

            if((touchAreaIndex == DOOR_ACTUATOR_AREA_1) || (touchAreaIndex == DOOR_ACTUATOR_AREA_2)) {
                DealWithDoorActuatorButtons(touchAreaIndex, usbDevice, usbHostGC);

                dealtWithTouch = true;
            }
        } // Open door

        if(!dealtWithTouch) {
            NetworkParameters *networkParameters = NetworkParameters::GetInstance(usbDevice, usbHostGC);
            
            if(networkParameters != NULL) {
             
                if(networkParameters->TouchAreaIsNetworkParameter(touchAreaIndex)) {
                 
                    networkParameters->DealWithTouch(touchAreaIndex);
                    
                    dealtWithTouch = true;
                }
            }
        } // Network parameter

#define USE_GAS_CALIBRATION_PAGE_HANDLER
#ifdef USE_GAS_CALIBRATION_PAGE_HANDLER
        if(!dealtWithTouch) {
            GasCalibrationPageHandler *gasCalibrationPageHandler = GasCalibrationPageHandler::GetInstance(usbDevice, usbHostGC);
            
            if(gasCalibrationPageHandler != NULL) {
             
                if(gasCalibrationPageHandler->TouchAreaIsOnCalibrationPage(touchAreaIndex)) {
                 
                    dealtWithTouch = gasCalibrationPageHandler->DealWithTouch(touchAreaIndex);
                }
            }
        } // Touch area on Gas Calibration page
#undef USE_GAS_CALIBRATION_PAGE_HANDLER
#endif // USE_GAS_CALIBRATION_PAGE_HANDLER

        if(!dealtWithTouch) {
            ColumnDHAutoCalibrationPageHandler *columnDHAutoCalibrationPageHandler = ColumnDHAutoCalibrationPageHandler::GetInstance(usbDevice, usbHostGC);
            
            if(columnDHAutoCalibrationPageHandler != NULL) {
             
                if(columnDHAutoCalibrationPageHandler->TouchAreaIsOnCalibrationPage(touchAreaIndex)) {
                 
                    dealtWithTouch = columnDHAutoCalibrationPageHandler->DealWithTouch(touchAreaIndex);
                }
            }
        } // Touch area on DH Column Auto Calibration page

        if(!dealtWithTouch) {
            ColumnDHManualCalibrationPageHandler *columnDHManualCalibrationPageHandler = ColumnDHManualCalibrationPageHandler::GetInstance(usbDevice, usbHostGC);
            
            if(columnDHManualCalibrationPageHandler != NULL) {
             
                if(columnDHManualCalibrationPageHandler->TouchAreaIsOnCalibrationPage(touchAreaIndex)) {
                 
                    dealtWithTouch = columnDHManualCalibrationPageHandler->DealWithTouch(touchAreaIndex);
                }
            }
        } // Touch area on DH Column Manual Calibration page

        if(!dealtWithTouch) {
            ColumnDHSensorCalibrationPageHandler *columnDHSensorCalibrationPageHandler = ColumnDHSensorCalibrationPageHandler::GetInstance(usbDevice, usbHostGC);
            
            if(columnDHSensorCalibrationPageHandler != NULL) {
             
                if(columnDHSensorCalibrationPageHandler->TouchAreaIsOnCalibrationPage(touchAreaIndex)) {
                 
                    dealtWithTouch = columnDHSensorCalibrationPageHandler->DealWithTouch(touchAreaIndex);
                }
            }
        } // Touch area on DH Column Sensor Calibration page

        if(!dealtWithTouch) {
            ColumnDHPSUDACPageHandler *columnDHPSUDACPageHandler = ColumnDHPSUDACPageHandler::GetInstance(usbDevice, usbHostGC);
            
            if(columnDHPSUDACPageHandler != NULL) {
             
                if(columnDHPSUDACPageHandler->TouchAreaIsOnPSUDACPage(touchAreaIndex)) {
                 
                    dealtWithTouch = columnDHPSUDACPageHandler->DealWithTouch(touchAreaIndex);
                }
            }
        } // Touch area on DH PSU DAC page

        if(!dealtWithTouch) {
            NudgeAndDampPageHandler *nudgeAndDampPageHandler = NudgeAndDampPageHandler::GetInstance(usbDevice, usbHostGC);
            
            if(nudgeAndDampPageHandler != NULL) {
             
                if(nudgeAndDampPageHandler->TouchAreaIsOnNudgeAndDampPage(touchAreaIndex)) {
                 
                    dealtWithTouch = nudgeAndDampPageHandler->DealWithTouch(touchAreaIndex);
                }
            }
        } // Touch area on one of the Nudge and Damp pages

        if(!dealtWithTouch) {
            DebugCommandsPageHandler *debugCommandsPageHandler = DebugCommandsPageHandler::GetInstance(usbDevice, usbHostGC);
            
            if(debugCommandsPageHandler != NULL) {
             
                if(debugCommandsPageHandler->TouchAreaIsOnDebugCommandsPage(touchAreaIndex)) {
                 
                    dealtWithTouch = debugCommandsPageHandler->DealWithTouch(touchAreaIndex);
                }
            }
        } // Touch area on the GC (Debug) Commands page

        if(!dealtWithTouch) { // Try Column Oven Fan page - no 'PageHandler' class for this -
                              // trivial - only two touch areas/buttons, each of which simply sends
                              // a command (without arguments) to the GC - no values displayed
            if(touchAreaIndex == COLUMN_OVEN_FAN_NORMAL) {
                ExecuteGCCommand("CFNO",usbDevice, usbHostGC);
                dealtWithTouch = true;
            } else if(touchAreaIndex == COLUMN_OVEN_FAN_COOLING) {
                ExecuteGCCommand("CFCO",usbDevice, usbHostGC);
                dealtWithTouch = true;
            }
        }
        
        if(!dealtWithTouch) {
            GasBackPressureDACPageHandler *gasBackPressureDACPageHandler = GasBackPressureDACPageHandler::GetInstance(usbDevice, usbHostGC);
            
            if(gasBackPressureDACPageHandler != NULL) {
             
                if(gasBackPressureDACPageHandler->TouchAreaIsOnGasBackPressureDACPage(touchAreaIndex)) {
                 
                    dealtWithTouch = gasBackPressureDACPageHandler->DealWithTouch(touchAreaIndex);
                }
            }
        } // Touch area on Gas Backpressure DAC page

        if(!dealtWithTouch) {
            GasChannelDACAndADCPageHandler *gasChannelDACAndADCPageHandler = GasChannelDACAndADCPageHandler::GetInstance(usbDevice, usbHostGC);
            
            if(gasChannelDACAndADCPageHandler != NULL) {
             
                if(gasChannelDACAndADCPageHandler->TouchAreaIsOnGasChannelDACAndADCPage(touchAreaIndex)) {
                 
                    dealtWithTouch = gasChannelDACAndADCPageHandler->DealWithTouch(touchAreaIndex);
                }
            }
        } // Touch area on Gas channel DAC and ADC page

        if(!dealtWithTouch) {
            NumericKeypadPageHandler *numericKeypadPageHandler = NumericKeypadPageHandler::GetInstance(usbDevice, usbHostGC);
            
            if(numericKeypadPageHandler != NULL) {
             
                if(numericKeypadPageHandler->TouchAreaIsOnNumericKeypadPage(touchAreaIndex)) {
                 
                    dealtWithTouch = numericKeypadPageHandler->DealWithTouch(touchAreaIndex);
                }
            }
        } // Touch area on numeric keypad page

        if(!dealtWithTouch) {
            EthernetKeypadPageHandler *ethernetKeypadPageHandler = EthernetKeypadPageHandler::GetInstance(usbDevice, usbHostGC);
            
            if(ethernetKeypadPageHandler != NULL) {
             
                if(ethernetKeypadPageHandler->TouchAreaIsOnEthernetKeypadPage(touchAreaIndex)) {
                 
                    dealtWithTouch = ethernetKeypadPageHandler->DealWithTouch(touchAreaIndex);
                }
            }
        } // Touch area on numeric keypad page
        
        if(!dealtWithTouch) {
            
            DetectorIgnitionHandler *detectorIgnitionHandler = DetectorIgnitionHandler::GetInstance(usbDevice, usbHostGC);
            
            if(detectorIgnitionHandler != NULL) {
             
                if(detectorIgnitionHandler->TouchAreaIsDetectorIgniteButton(touchAreaIndex)) {
                 
                    dealtWithTouch = detectorIgnitionHandler->DealWithTouch(touchAreaIndex);
                }
            }
        } // Handling detector ignition
        
        if(!dealtWithTouch) {
#ifdef SERVICE_INTERVALS_ACTIVE
            if(ServiceInterval::IsServicedTouchArea(touchAreaIndex)) {
                
                ServiceInterval::DealWithServicedTouchArea(touchAreaIndex);
                
                // This is a ServiceInterval touch area -
                // nothing else can deal with it,
                // whether we actually did anything or not
                dealtWithTouch = true;
            }
#endif // SERVICE_INTERVALS_ACTIVE
        } // Service interval
        
        if(!dealtWithTouch) {
            if(touchAreaIndex == COLUMN_METHOD_SCROLL_UP) {
                getGCStatusLoop->ScrollColumnMethodRampsUpIfPossible();
                dealtWithTouch = true;
            } else if(touchAreaIndex == COLUMN_METHOD_SCROLL_DOWN) {
                getGCStatusLoop->ScrollColumnMethodRampsDownIfPossible();
                dealtWithTouch = true;
            } else if(touchAreaIndex == INJECTOR_METHOD_SCROLL_UP) {
                getGCStatusLoop->ScrollInjectorMethodRampsUpIfPossible();
                dealtWithTouch = true;
            } else if(touchAreaIndex == INJECTOR_METHOD_SCROLL_DOWN) {
                getGCStatusLoop->ScrollInjectorMethodRampsDownIfPossible();
                dealtWithTouch = true;
            } else if(touchAreaIndex == GAS_METHOD_SCROLL_UP) {
                getGCStatusLoop->ScrollGasMethodRampsUpIfPossible();
                dealtWithTouch = true;
            } else if(touchAreaIndex == GAS_METHOD_SCROLL_DOWN) {
                getGCStatusLoop->ScrollGasMethodRampsDownIfPossible();
                dealtWithTouch = true;
            }
        }
    }
}
   

/*
    Waits (indefinitely) for a USB device to be connected -
    note that, if there is no USB device, we will hang at this point -
    there is no timeout. 

    Args are: a pointer to the USBHost instance on which we are to look for the USB device
    
    Returns a pointer to the USB device
*/
USBDeviceConnected* GetUSBDevice(USBHost* usbHost)
{
    USBDeviceConnected* usbDevice = NULL;

    while(usbDevice == NULL) {
        for (uint8_t i = 0; i < MAX_DEVICE_CONNECTED; ++i) {
            usbDevice = usbHost->getDevice(i);

            if (usbDevice) {
                return usbDevice;
            }
        }
    }
    
    return NULL;
}

/*
    Function that executes the easyGUI code in VarInit.c, to initialise all the easyGUI variables
    to the values we have specified in the easyGUI application.
*/
static void InitialiseAllEasyGUIVariables(void)
{
#include "VarInit.h" // Renamed from the easyGUI file "VarInit.c", so as not to confuse the mbed compiler
}
    

/*
    Sets up the easyGUI user interface in an acceptable initial state.

    No return code.
*/
void InitEasyGUIDisplay(int initialEasyGUIPage)
{
    // easyGUI stuff - note function calls require 'extern "C"' in relevant header
    
    // Need to set up heat on/off command easyGUI variable 
    // before we display the home page for the first time - 
    // but we do not have a heat control object at this point - 
    // default to 'Heat On'
    strcpy(GuiVar_heatOnOffCommand, "Heat On");
#ifdef USE_HEAT_ONOFF_BUTTON_BITMAPS
    DrawHeatOnOffButton();
#endif
    
#ifdef USE_QSPI_BITMAPS
    // Before we display the first page - needs BootScreen
    qspiBitmaps.SetupArray();
#endif
    
    GuiDisplay_Lock();
    
    GuiLib_Init();
  
    DisplayEasyGuiStructure(initialEasyGUIPage, NULL, NULL);
  
    GuiDisplay_Unlock();
}

void InitialiseLPC4088(DMBoard* board, void **frameBufferAddress1, void **frameBufferAddress2)
{
    RtosLog* log = board->logger();
    Display* disp = board->display();
  
    DMBoard::BoardError err;

    do {
        err = board->init();
        if (err != DMBoard::Ok) {
            log->printf("Failed to initialize the board, got error %d\r\n", err);
            break;
        }
       
#ifdef MEMORY_TEST_OCT_2016
        for (int ind = 0; ind < clogLength; ++ind) {
            clogUpMemory[ind] = 'A';
        }
#endif // MEMORY_TEST_OCT_2016


#define COLOR_FLICKERING_FIX_1
#ifdef COLOR_FLICKERING_FIX_1
        // Possible fix for display flickering on LPC4088
        uint32_t* reg = ((uint32_t*)0x400fc188);
        *reg |= (3<<10);
#undef COLOR_FLICKERING_FIX_1
#endif
        log->printf("\n\nHello World!\n\n");
    
        void* fb = disp->allocateFramebuffer();
        if (fb == NULL) {
            log->printf("Failed to allocate memory for a frame buffer\r\n");
            err = DMBoard::MemoryError;
            break;
        }
    
        *frameBufferAddress1 = fb;

        // Allocate a second frame buffer - what will its address be?
        void* fb2 = disp->allocateFramebuffer();
        *frameBufferAddress2 = fb2;
                
        Display::DisplayError disperr;
//      disperr = disp->powerUp(fb, Display::Resolution_24bit_rgb888);
        // Start display in default mode (16-bit) (24-bit uses too much memory)
#define COLOR_FLICKERING_FIX_2
#ifdef COLOR_FLICKERING_FIX_2
        // Second possible fix for colour flickering problem,
        // suggested by Embedded Artists - specify low frame rate
        disp->powerDown();
        disperr = disp->powerUp(fb, Display::Resolution_16bit_rgb565, FrameRate_Low);
#undef COLOR_FLICKERING_FIX_2
#else
        disperr = disp->powerUp(fb);
#endif
        if (disperr != Display::DisplayError_Ok) {
            log->printf("Failed to initialize the display, got error %d\r\n", disperr);
            break;
        }
        
#define TEST_BACKLIGHT
#ifdef TEST_BACKLIGHT
        disp->backlight(100);
#endif // TEST_BACKLIGHT
    
    } while(false);

    if (err != DMBoard::Ok) {
        log->printf("\nTERMINATING\n");
        wait_ms(2000); // allow RtosLog to flush messages
        mbed_die();
    }  
}

int main()
{
    DMBoard* board = &DMBoard::instance();
    void *frameBuffer1;
    void *frameBuffer2;
    InitialiseLPC4088(board, &frameBuffer1, &frameBuffer2);
    GuiDisplay_SetFrameAddress(frameBuffer1);
    
    SwimDraw* swimDrawInstance = SwimDraw::GetInstance();
    if(swimDrawInstance != NULL) {
        swimDrawInstance->Initialise(board, frameBuffer1);
    }
    
    InitialiseAllEasyGUIVariables();
    
    // The first thing we do is try to connect to the GC - 
    // so tell the user this first of all...
    InitEasyGUIDisplay(GuiStruct_GCConnectionPage_Def);
    
    
    SetupEasyGUIBuildDateVariable();
    
    
    char dbgBuff[100];
    sprintf(dbgBuff, "FrameBuffers are: %X, %X", frameBuffer1, frameBuffer2);
    EasyGUIDebugPrint(dbgBuff, 100, 100);
  
    // Now the USB 'stuff'
  
    USBHost* usbHost = USBHost::getHostInst();
    USBHostGC usbHostGC;

    DrawErrorMessage("Waiting for USB device...");
    // Note that 'GetUSBDevice' will not return until it finds a USB device -
    // it does *not* timeout
    USBDeviceConnected* usbDevice = GetUSBDevice(usbHost);
    
    DrawErrorMessage("                                            ");

    if(usbDevice != NULL) {
        USB_TYPE enumerateRetVal = usbHost->enumerate(usbDevice, &usbHostGC);

        if (usbHostGC.DeviceIsGC()) {
            if (usbHostGC.AttachGCDevice(usbDevice)) {

                DrawErrorMessage("Found GC device                                      ");
                

#ifdef OLD_TOUCH_LISTENER
                SetupUSBGCTouchListener(board, usbDevice, &usbHostGC);
                
                DrawErrorMessage("After call to SetupUSBGCTouchListener                ");
#endif

                getGCStatusLoop = GetGCStatusLoop::GetInstance(usbDevice, &usbHostGC);
                
                DrawErrorMessage("After creation of GetGCStatusLoop instance           ");
                
                
                theGCHeatControl = new GCHeatControl(usbDevice, &usbHostGC);
                
#ifdef USE_HEAT_ONOFF_BUTTON_BITMAPS
                DrawHeatOnOffButton();
#else
                UpdateHeatOnOffEasyGuiVariable();
#endif // USE_HEAT_ONOFF_BUTTON_BITMAPS

                DrawErrorMessage("After UpdateHeatOnOffEasyGuiVariable                 ");
                
                                
                getGCStatusLoop->SetHomePageGCComponentStatusColorAreas(&homePageGCComponentStatusColorAreas);
                getGCStatusLoop->SetSingleGCComponentPageStatusColorAreas(&singleGCComponentPageStatusColorAreas);

                DrawErrorMessage("Point 1                                              ");
                
#ifdef SERVICE_INTERVALS_ACTIVE
                ServiceInterval::SetupAllServiceIntervals();
                // Note these two functions *** have different names ***
                ServiceInterval::StartAllServiceIntervals();
#endif // SERVICE_INTERVALS_ACTIVE
    
                DrawErrorMessage("Point 2                                              ");
                
                getGCStatusLoop->SetupAllEasyGUIVariables();
                SetupDoorActuatorCommandUserInterface(usbDevice, &usbHostGC, true);
    
                DrawErrorMessage("Point 3                                              ");
#ifdef USE_QSPI_BITMAPS
                getGCStatusLoop->SetQSPIBitmaps(&qspiBitmaps);
// Test - what does it do if it cannot find the bitmaps??
#endif
                DrawErrorMessage("Point 4                                              ");
                
                getGCStatusLoop->SetCurrentPage(GuiStruct_HomePage_1);
                
                DrawErrorMessage("Point 5                                              ");
                
                //DisplayEasyGuiStructure(GuiStruct_HomePage_1, usbDevice, &usbHostGC);

                DebugPrint("Before MainLoopWithEthernet", 100, 450);

              // Currently, this never returns - but it allows time for the TouchCallback function to be invoked
                //getGCStatusLoop->MainLoop(board);
                getGCStatusLoop->MainLoopWithEthernet(board);
                
                // Should never reach this code - but just in case...
                delete theGCHeatControl;

            } else {
                DrawErrorMessage("Failed to attach GC device to host                                ");
            }
        } else {
            DrawErrorMessage("  *** USB device found, is *not* a GC ***                                ");
        }
    } else {
        DrawErrorMessage("  *** No USB device found ***                        ");
    }
  
    while(true) {}
}