Repository for import to local machine

Dependencies:   DMBasicGUI DMSupport

GetGCStatusLoop.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 "EthernetInterface.h"
#include "NetworkParameters.h"

#include <string.h>
#include <float.h>

#include "GetGCStatusLoop.h"
#include "SettingsHandler.h"
#include "ServiceInterval.h"
#include "GCRealTimeClock.h"
#include "ColumnDHAutoCalibrationPageHandler.h"
#include "ColumnDHPSUDACPageHandler.h"
#include "SwimDraw.h"
#include "USBHostGCUtilities.h"

#include "GuiLib.h"


#define USE_LED_FOR_DEBUGGING

#ifdef USE_LED_FOR_DEBUGGING

#include "gpio_api.h"
#include "wait_api.h"
#include "toolchain.h"
#include "mbed_interface.h"

// We turn on LED 2 during Send Method
static void SetLed2(bool turnLedOn)
{
    gpio_t led_2; gpio_init_out(&led_2, LED2);

    if(turnLedOn) {
        gpio_write(&led_2, 0); // zero appears to mean "turn LED 2 on"
    } else {
        gpio_write(&led_2, 1); // one appears to turn it off
    }
}

// We turn on LED 3 when we have sent a message to the GC (received from the Ethernet thread)
// and we are waiting for its response
static void SetLed3(bool turnLedOn)
{
    gpio_t led_3; gpio_init_out(&led_3, LED3);

    if(turnLedOn) {
        gpio_write(&led_3, 1); // one appears to mean "turn LED 3 on"
    } else {
        gpio_write(&led_3, 0); // zero appears to turn it off
    }
}

#endif // USE_LED_FOR_DEBUGGING


/*
    Displays the specified text string at the specified location in the currently-displayed easyGUI page.
    
    Defined in main.cpp
*/
extern void EasyGUIDebugPrint(char *stuffToPrint, short X, short Y);

/*
    Displays the specified text string at the specified location in the currently-displayed easyGUI page.
    
    Defined in main.cpp - and omits the 'ALLOW_DEBUG_PRINTS' #define
*/
extern void SpecialDebugPrint(char *stuffToPrint, GuiConst_INT16S X, GuiConst_INT16S Y);


// Version of the above that increments, and displays, a counter each time it is called
extern void EasyGUIDebugPrintWithCounter(char *stuffToPrint, short X, short Y);

/*
    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).
    
    Defined in main.cpp
*/
extern GuiConst_INTCOLOR SixteenBitColorValue(GuiConst_INT8U red, GuiConst_INT8U green, GuiConst_INT8U blue);

/*
    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.
    
    Defined in main.cpp
*/
extern void DrawRunButton(bool enabled);

/*
    Draws the Heat On/Off button in the correct place, in the correct state (i.e. saying "Heat On"
    if it is off, and vice versa - i.e. the text states what the button will do if pressed).
    No arguments (finds out the current heat on/off state for itself)
    No return code.
    
    Defined in main.cpp
*/
extern void DrawHeatOnOffButton(void);


/*
    Draws the background bitmap - without the Ellutia logo. It fills the screen, so you do not need to call GuiLib_Clear.
    Defined in main.cpp
*/
extern void DrawBackgroundBitmap(void);
#define USING_BACKGROUND_BITMAP

/*
    Same as the above, but draws the background bitmap *with* the Ellutia logo.
    Defined in main.cpp
*/
void DrawBackgroundBitmapWithLogo(void);


/*
    The colour of the main part of the (fake) bitmap - also from main.cpp
*/
extern GuiConst_INTCOLOR GetFakeBackgroundBitmapMainColour(void);

/*
    Depending on the argument passed to it,this function either draws the word "Stabilising" 
    at the bottom of the display, in the centre, in green, or erases it.
    
    Arg is: true to display the text, false to erase it
    
    No return code.
    
    Defined in main.cpp
*/
extern void DrawStabilisingMessage(bool stabilising);

/*
    Depending on the argument passed to it,this function either draws the word "Equilibrating" 
    at the bottom of the display, in the centre, in amber, or erases it.
    
    Arg is: true to display the text, false to erase it
    
    No return code.
    
    Defined in main.cpp
*/
extern void DrawEquilibratingMessage(bool equilibrating);

/*
    Depending on the argument passed to it,this function either draws the word "Cooling" 
    at the bottom of the display, in the centre, in light blue, or erases it.
    
    Arg is: true to display the text, false to erase it
    
    No return code.
*/
extern void DrawCoolingMessage(bool cooling);

/*
    Depending on the argument passed to it,this function either draws the word "Ready" 
    at the bottom of the display, in the centre, in green, or erases it.
    
    Arg is: true to display the text, false to erase it
    
    No return code.
*/
extern void DrawReadyMessage(bool ready);


/*
    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.
    
    Defined in main.cpp
*/
bool IsStartOfMethodCommand(char *gcCommand);

/*
    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.
    
    Defined in main.cpp
*/
bool IsEndOfMethodCommand(char *gcCommand);



/*
    The original TouchCallback function, called directly by ListenerFunction in the original TouchListener class.
    
    Defined in main.cpp.
    
    TODO: Move into this class? Rationalise, at least...
*/
extern void TouchCallback(touch_coordinate_t touchCoords, USBDeviceConnected* usbDevice, USBHostGC* usbHostGC, int tickCount, bool newTouch);


/*
    A function to deal with the 'admin' required when the GC starts running - 
    i.e. setting up the relevant easyGUI variables, displaying the correct page, etc.
    
    Defined in main.cpp
*/
extern void SetupForStartOfRun(USBDeviceConnected* usbDevice, USBHostGC* usbHostGC);


/*
    Reads the current state of the door actuators, and sets up the buttons 
    at the bottom of the page appropriately. 
    
    Defined in main.cpp
*/
extern void SetupDoorActuatorCommandUserInterface(USBDeviceConnected* usbDevice, USBHostGC* usbHostGC, bool beforePageDisplay);

/*
    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

    Defined in main.cpp
*/
extern bool DoorActuatorButtonsHaveChanged(USBDeviceConnected* usbDevice, USBHostGC* usbHostGC);


/*
    Sets up the easyGUI variable that controls the colour of the door lock/unlock command.
    
    Defined in main.cpp
*/
extern void SetupDoorActuatorCommandColour(USBDeviceConnected* usbDevice, USBHostGC* usbHostGC, bool actuatorsAreMoving);


/*
    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
          X coords of the left and right edges of the section
          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).
    
    Defined in main.cpp
*/
extern bool DrawProfileSectionUsingGuiLibVLine(GuiConst_INTCOLOR colour, GuiConst_INT16S xLeft, GuiConst_INT16S xRight, GuiConst_INT16S yBottom, GuiConst_INT16S yTopLeft, GuiConst_INT16S yTopRight);


/*
    Function to cause the LPC4088 to reboot.
    
    Defined in main.cpp.
*/
extern void reboot();


// Note that GetGCStatusLoop is a singleton - we do not need or want there to be more than one instance of it
// (there is only one GC, and only one LPC4088).
// This is the one and only GetGCStatusLoop instance
GetGCStatusLoop * GetGCStatusLoop::theGetGCStatusLoop = NULL;

//const int GetGCStatusLoop::waitTimeMs = 1000;
const int GetGCStatusLoop::waitTimeMs = 500; 
//const int GetGCStatusLoop::waitTimeMs = 200;
const int GetGCStatusLoop::shortWaitTimeMs = 50; // For use by the thread_wait calls that look for Ethernet transactions
                                                 // and touch events at multiple points in the 'main loop'

#define USE_THREAD_WAIT // Thread::wait lets other threads run while this one is paused
                        // wait_ms (the other option) blocks all other threads while waiting - not a good idea.
                        // Actually now using Thread::signal_wait, as part of restructuring touch event code
                        // (see also the SimplifiedTouchListener class) - and Ethernet code (see EthernetHandler).

const float GetGCStatusLoop::graphBarIntervalMinutes = 1.0f; // Units are minutes...
const float GetGCStatusLoop::graphBarIntervalSeconds = 0.1f; // ...in both cases

const float GetGCStatusLoop::methodTimeUnitsThreshold = 5.0f; // If the method takes less than this number of minutes to run,
                                                              // use seconds as the time units we display on the profile graphs,
                                                              // otherwise use minutes

// Timer stuff - trying to avoid responding to the same touch event more than once
const uint32_t GetGCStatusLoop::timerIntervalMilliSec = 600; // i.e. 0.6 second
const uint32_t GetGCStatusLoop::minTimerTicksBetweenTouchEvents = 5; // i.e. 1 second
// Increase timer tick interval - does this make Ethernet comms more responsive?
//const uint32_t GetGCStatusLoop::timerIntervalMilliSec = 500; // i.e. 0.5 second
//const uint32_t GetGCStatusLoop::minTimerTicksBetweenTouchEvents = 2; // i.e. 1 second
// Answer - no - possibly worse, in fact
int GetGCStatusLoop::timerTickCount = 0;

// Trying different ways of preventing the same touch event being processed more than once
//#define MULTI_TOUCH_TECHNIQUE_1 // Enforce a minimum time interval (i.e. number of ticks) between the touch events we respond to
//#define MULTI_TOUCH_TECHNIQUE_2 // Look for successive touch events with different X and/or Y coordinates
//#define MULTI_TOUCH_TECHNIQUE_3 // Have at least one timeout or timer tick (and now, Ethernet message) between touch events
//#define MULTI_TOUCH_TECHNIQUE_4 // Use a Timer object (not an RTosTimer, like MULTI_TOUCH_TECHNIQUE_1) to enforce a minimum time interval between touches
// The above are mutually exclusive - do not un-comment more than one at the same time 
// (you can comment them all out if you wish to see the original problem)
// At present (07 Oct 2016), MULTI_TOUCH_TECHNIQUE_4 seems to be the clear winner - far more effective than the others
// Although now (04 Nov 2016) itself superseded by MULTI_TOUCH_TECHNIQUE_5, in SimplifiedTouchListener

#ifdef MULTI_TOUCH_TECHNIQUE_1
osThreadId GetGCStatusLoop::timerCallbackThreadToSignal;
#endif // MULTI_TOUCH_TECHNIQUE_1

/*
    Convenient default values for Ethernet parameters
*/
//#define FOR_GRAHAM_SEWELL
#ifdef FOR_GRAHAM_SEWELL
const int   GetGCStatusLoop::defaultEthernetPort = 3456;
const char* GetGCStatusLoop::defaultEthernetIP = "192.168.111.100";
const char* GetGCStatusLoop::defaultEthernetMask = "255.255.255.0";
const char* GetGCStatusLoop::defaultEthernetGateway = "192.168.111.254";
#else // Mine
const int   GetGCStatusLoop::defaultEthernetPort = 3456;
const char* GetGCStatusLoop::defaultEthernetIP = "192.168.1.100";
const char* GetGCStatusLoop::defaultEthernetMask = "255.255.255.0";
const char* GetGCStatusLoop::defaultEthernetGateway = "192.168.1.254";
#endif

// Text for the "Start/Exit Calibration" button on the DH Column Calibration page
const char* GetGCStatusLoop::startDHColumnCalibration = "Start Calibration";
const char* GetGCStatusLoop::exitDHColumnCalibration  = "Exit Calibration";

// On the easyGUI pages/structures, character code 161 appears as the degree symbol (the little circle at the top left of the character position). See the "Font Editing" page (accessed with the F4 key).
// To type it into NotePad, enable Num Lock on the keyboard, hold down the Alt key, type 0161 on the numeric keypad, then release the Alt key.
// Note that a different character ("¡") then appears in NotePad. If you copy and paste that character into a string in easyGUI, it appears as "¡",
// but in an easyGUI "structure", it appears as the degree symbol. (I don't understand it either.)
// The following is the only way I could find to use the 161 character in code that (a) works, and (b) the compiler does not complain about.
const char GetGCStatusLoop::degSymbol = 161; 
const char GetGCStatusLoop::stringFormatdegCUnits[6] = { '%', 's', ' ', 161, 'C', '\0' };
// Functions to make the above available to the rest of the world, in a controlled manner
const char *GetGCStatusLoop::GetDegCUnitsWithSpace(void)
{
    return &stringFormatdegCUnits[2];
}
const char *GetGCStatusLoop::GetDegCUnitsWithoutSpace(void)
{
    return &stringFormatdegCUnits[3];
}
#define TRY_DEG_SYMBOL


//#define USING_DATASET_4 // This is the 'dot at current time' dataset, used in the profile graphs we display while the GC is running.
                        // Applies to the 'running column' and 'running gas' graphs. Omit when we make the 'before now' bars thicker
                        // than the 'after now' bars. Without the 'dot at current time', we do not have to clear the graph 
                        // when we redraw it - reduces flickering (since the bars are now overwriting themselves)
                        
//#define USING_DATASET_4_ON_NON_RUNNING_GRAPHS // Since these graphs do not show a 'current time',
                                              // dataset 4 serves little purpose on these graphs either
                                              
#define PTV_RAMPS_AVAILABLE // #define this if the GC software includes the commands to set up the PTV ramps - i.e. it is at least version 3.90.
                            // If not, comment it out.
                            
#define UPDATE_PAGES_CONTINUOUSLY // #define this to cause pages that display data to be updated/redisplayed whether or not the GC status has changed
                                  // (otherwise they will be redisplayed only if the status has changed, and the displayed temperatures may get out of date)
    
#define DO_NOTHING_ELSE_WHILE_SENDING_METHOD // #define this to disable all other 'events' while downloading a method from Ellution to the GC.
                                             // (we need this to be as fast as possible)       

#define TEST_GUILIB_VLINE_PROFILES // #define this to use the experimental functions that draw a solid profile direct to the display,
                                   // without using the easyGUI graph functions

//#define WANT_STATUS_RECTANGLE_ON_COLUMN_AUTO_CALIB_PAGE // Now moved to Servicing pages - surely don't want status rectangles there?
//#define WANT_STATUS_RECTANGLE_ON_GAS_CALIB_PAGES        // ....
//#define WANT_COLUMN_STATUS_RECTANGLE
//#define WANT_DETECTOR_STATUS_RECTANGLE
//#define WANT_INJECTOR_STATUS_RECTANGLE
//#define WANT_GAS_STATUS_RECTANGLE
//#define WANT_DOOR_ACTUATOR_BUTTONS_ON_COLUMN_PAGES
//#define WANT_COMPONENT_ICON_ON_PROFILE_PAGES

/*
    Singleton class - return the one and only instance, first creating it if necessary.
    
    The instance is passed pointers to the USBHostGC instance and the USB device corresponding with the GC.
*/
GetGCStatusLoop *GetGCStatusLoop::GetInstance(USBDeviceConnected* newUsbDevice, USBHostGC* newUsbHostGC)
{
    if (theGetGCStatusLoop == NULL) {
        theGetGCStatusLoop = new GetGCStatusLoop(newUsbDevice, newUsbHostGC);
    }
    return theGetGCStatusLoop;
}

/*
  Static method to return the one and only GetGCStatusLoop instance, if it exists. 
  If not, this function will return NULL - it will *not* create a new instance.
  
  Caller must check for NULL
  **************************
*/
GetGCStatusLoop *GetGCStatusLoop::GetInstance(void)
{
    return theGetGCStatusLoop;
}

// Singleton class - private constructor
GetGCStatusLoop::GetGCStatusLoop(USBDeviceConnected* newUsbDevice, USBHostGC* newUsbHostGC)
{
    usbDevice = newUsbDevice;
    usbHostGC = newUsbHostGC;
    
    currentPage = 9999; // Impossible value
    
    pageJustChanged = false;
    displayingData = false;
    needToUpdateProfileGraphs = false;
    
    ethernetPort = defaultEthernetPort;
    strcpy(ethernetIP, defaultEthernetIP);
    strcpy(ethernetMask, defaultEthernetMask);
    strcpy(ethernetGateway, defaultEthernetGateway);
    useDHCPForEthernet = false;
    
    gcInStandbyMode = false;
    
    realGCIsRunning = false;
    
    sendingMethod = false;
    
    homePageGCComponentStatusColorAreas = NULL;
    singleGCComponentPageStatusColorAreas = NULL;
    
    runningPage1ProgressBar = new ProgressBar(100, 250, 600, 50, horizontal, 100.0, // We will change the calibrated range at the start of every run
                                                SixteenBitColorValue(0xFF, 0, 0),       // bar colour - red
                                                GetFakeBackgroundBitmapMainColour(),    // background colour - as used in the fake background bitmap, drawn in main.cpp
                                                0                                       // border colour - black
                                                );
    
    CreateGuiLibGraphsAndDataSets();
            
    SetupColumnAndInjectorAndGasProfileData();
    
    previousColumnMethodRunTime = -99.0f;
    columnMethodFinished = false;
    
    previousGasMethodRunTime = -99.0f;
    gasMethodFinished = false;
    
    gotAtLeastOneTimeout = false;
    
    lastColumnStatusDisplayedOnHomePage = NONE;
    lastInjectorStatusDisplayedOnHomePage = NONE;
    lastDetectorStatusDisplayedOnHomePage = NONE;
    lastGasStatusDisplayedOnHomePage = NONE;
    
    lastColumnStatusDisplayedOnColumnPage = NONE;
    lastInjectorStatusDisplayedOnInjectorPage = NONE;
    lastDetectorStatusDisplayedOnDetectorPage = NONE;
    lastGasStatusDisplayedOnGasInformationPage = NONE;

    lastSimplifiedGCState = GC_IDLE;
    
    runWasAborted = false;
    
    handlingEthernetMessage = false;
    handlingTouchEvent = false;
    
#ifdef MULTI_TOUCH_TECHNIQUE_1
    // rtosTimer stuff
    rtosTimer = new RtosTimer(GetGCStatusLoop::TimerCallback);
    rtosTimer->start(timerIntervalMilliSec);
    lastTouchEventTickCount = 0;
    timerCallbackThreadToSignal = osThreadGetId();
#endif // MULTI_TOUCH_TECHNIQUE_1

#ifdef MULTI_TOUCH_TECHNIQUE_2
    lastTouchEventX = 9999;
    lastTouchEventY = 9999;
#endif // MULTI_TOUCH_TECHNIQUE_2

#ifdef MULTI_TOUCH_TECHNIQUE_4
    touchTimer.stop();
    touchTimer.reset();
    touchTimer.start();
#endif // MULTI_TOUCH_TECHNIQUE_4

    qspiBitmaps = NULL;
    
    columnMethodRampData = NULL; // Create this only when we need it
    columnMethodPageScrollIndex = 0;
    previousColumnMethodPageScrollIndex = columnMethodPageScrollIndex;
    
    injectorMethodRampData = NULL; // Create this only when we need it
    injectorMethodPageScrollIndex = 0;
    previousInjectorMethodPageScrollIndex = injectorMethodPageScrollIndex;
    
    gasMethodRampData = NULL; // Create this only when we need it
    gasMethodPageScrollIndex = 0;
    previousGasMethodPageScrollIndex = gasMethodPageScrollIndex;
}
    
// Private destructor also
GetGCStatusLoop::~GetGCStatusLoop()
{
    if(runningPage1ProgressBar != NULL) {
        delete runningPage1ProgressBar;
    }
    
    delete runningColumnPageGraph;
    
    delete runningColumnPageGraphCompleteProfileDataSet;
    delete runningColumnPageGraphDataSet0;
    delete runningColumnPageGraphDataSet1;
    delete runningColumnPageGraphDataSet2;
    delete runningColumnPageGraphDataSet3;
    delete runningColumnPageGraphDataSet4;


    delete runningGasPageGraph;
    
    delete runningGasPageGraphCompleteProfileDataSet;
    delete runningGasPageGraphDataSet0;
    delete runningGasPageGraphDataSet1;
    delete runningGasPageGraphDataSet2;
    delete runningGasPageGraphDataSet3;
    delete runningGasPageGraphDataSet4;


    delete injectorTempProfilePageGraph;

    delete injectorTempProfilePageGraphCompleteProfileDataSet;
    delete injectorTempProfilePageGraphDataSet0;
    delete injectorTempProfilePageGraphDataSet1;
    delete injectorTempProfilePageGraphDataSet2;
    delete injectorTempProfilePageGraphDataSet3;
    delete injectorTempProfilePageGraphDataSet4;


    delete gasFlowProfilePageGraph;

    delete gasFlowProfilePageGraphCompleteProfileDataSet;
    delete gasFlowProfilePageGraphDataSet0;
    delete gasFlowProfilePageGraphDataSet1;
    delete gasFlowProfilePageGraphDataSet2;
    delete gasFlowProfilePageGraphDataSet3;
    delete gasFlowProfilePageGraphDataSet4;


    delete columnTempProfilePageGraph;

    delete columnTempProfilePageGraphCompleteProfileDataSet;
    delete columnTempProfilePageGraphDataSet0;
    delete columnTempProfilePageGraphDataSet1;
    delete columnTempProfilePageGraphDataSet2;
    delete columnTempProfilePageGraphDataSet3;
    delete columnTempProfilePageGraphDataSet4;


    delete runningInjectorPageGraph;
    
    delete runningInjectorPageGraphCompleteProfileDataSet;
    delete runningInjectorPageGraphDataSet0;
    delete runningInjectorPageGraphDataSet1;
    delete runningInjectorPageGraphDataSet2;
    delete runningInjectorPageGraphDataSet3;
    delete runningInjectorPageGraphDataSet4;
    
    if(columnMethodRampData != NULL) {
        delete columnMethodRampData;
    }
    
    if(injectorMethodRampData != NULL) {
        delete injectorMethodRampData;
    }

    if(gasMethodRampData != NULL) {
        delete gasMethodRampData;
    }

#ifdef MULTI_TOUCH_TECHNIQUE_1
    rtosTimer->stop();
    delete rtosTimer;
#endif // MULTI_TOUCH_TECHNIQUE_1
}

void GetGCStatusLoop::SetQSPIBitmaps(QSPIBitmaps* ptrToQSPIBitmaps)
{
    qspiBitmaps = ptrToQSPIBitmaps;
}
    

/*
    Displays the column lock and release buttons "on top of" the Lock/Unlock (originally Open Door) button 
    defined in easyGUI, and displayed on each of the column pages. This allows us to give the user
    two options (Lock or Release) when we complete the Close operation - the rest of the time 
    we display only one option, and this is how the easyGUI pages for the column are set up.
    Since it appears to be impossible to show or hide individual easyGUI controls at run time
    (i.e. each easyGUI page is fixed at design time) this seems to be the only way 
    to make a change to the user interface at run time.
    
    No arguments, no return code.
*/
void GetGCStatusLoop::DisplayColumnLockAndReleaseButtons(void)
{
    // Two adjoining rectangles on top of the current Lock/Unlock button
    GuiLib_BorderBox(248, 404, 400, 480, 0, SixteenBitColorValue(128, 128, 128)); // Black border, grey fill
    DisplayText("Lock", 324, 450, GuiLib_ALIGN_CENTER, GuiFont_Helv20Bold, GuiVar_doorActuatorCommandFGColour);
    GuiLib_BorderBox(400, 404, 552, 480, 0, SixteenBitColorValue(128, 128, 128)); // Black border, grey fill
    DisplayText("Release", 476, 450, GuiLib_ALIGN_CENTER, GuiFont_Helv20Bold, GuiVar_doorActuatorCommandFGColour);
}


void GetGCStatusLoop::SetupColumnAndInjectorAndGasProfileData(void)
{
    // Do this *** before *** setting up the injector and gas profiles
    SetupColumnTempProfilePageGraphDataFromGC();

    // Do this *** after *** setting up the column method
    SetupInjectorTempProfilePageGraphDataFromGC();

    // Do this *** after *** setting up the column method
    SetupGasFlowProfilePageGraphDataFromGC();
    
    // These easyGUI variables are intended to warn the user if we have 
    // no methods set up for these components - but we do now have methods for them
    GuiVar_columnTempProfilePageNoMethod[0] = '\0';
    GuiVar_injectorTempProfilePageNoMethod[0]= '\0';
    GuiVar_gasFlowProfilePageNoMethod[0]= '\0';
}

/*
    Called by the system in response to timer ticks.
    
    *** Needs to be kept as short as possible ***
*/
void GetGCStatusLoop::TimerCallback(void const * argument)
{
#ifdef MULTI_TOUCH_TECHNIQUE_1
    ++timerTickCount;
    
    osSignalSet(timerCallbackThreadToSignal, TIMER_TICK);    
#endif // MULTI_TOUCH_TECHNIQUE_1
}
    
/*
    Creates (but does not assign any data to) the objects that deal with the easyGUI graphs
    and their datasets. We should only need to create these once,
    but we may need to setup or change their data any number of times.
*/
void GetGCStatusLoop::CreateGuiLibGraphsAndDataSets(void)
{
    // The values passed to the GuiLibGraph constructor in creating each instance below
    // *must* *match* the graph indices assigned in the corresponding easyGUI 'structures' - 
    // there seems to be no way of getting these values from easyGUI at runtime,
    // so we must hard code them here
    runningColumnPageGraph = new GuiLibGraph(0);
    runningGasPageGraph = new GuiLibGraph(1);
    injectorTempProfilePageGraph = new GuiLibGraph(2);
    gasFlowProfilePageGraph = new GuiLibGraph(3);
    columnTempProfilePageGraph = new GuiLibGraph(4);
    runningInjectorPageGraph = new GuiLibGraph(6);


    // Now create the datasets for each graph. 
    //
    // In each case, the 'xxxGraphCompleteProfileDataSet' object contains the complete profile, but we do not display it (as such)
    // on the relevant graph. Instead, we make partial copies of it to each of the 'xxxGraphDataSetn' objects,
    // separating the points between the start and 'now' from the points between 'now' and the end of the method.
    // This allows us to assign these to different datasets in the relevant easyGUI graph, so that we can display
    // them in different colours and in different styles. 
    //
    // Having the complete profile in one dataset, then copying the relevant parts of it to the others, makes the code simpler, 
    // and easier to understand, than if we kept getting partial copies of the profile from the GC.
    //
    runningColumnPageGraphCompleteProfileDataSet = new GuiLibGraphDataSet;
    runningColumnPageGraphDataSet0 = new GuiLibGraphDataSet;
    runningColumnPageGraphDataSet1 = new GuiLibGraphDataSet;
    runningColumnPageGraphDataSet2 = new GuiLibGraphDataSet;
    runningColumnPageGraphDataSet3 = new GuiLibGraphDataSet;
    runningColumnPageGraphDataSet4 = new GuiLibGraphDataSet;

    runningGasPageGraphCompleteProfileDataSet = new GuiLibGraphDataSet;
    runningGasPageGraphDataSet0 = new GuiLibGraphDataSet;
    runningGasPageGraphDataSet1 = new GuiLibGraphDataSet;
    runningGasPageGraphDataSet2 = new GuiLibGraphDataSet;
    runningGasPageGraphDataSet3 = new GuiLibGraphDataSet;
    runningGasPageGraphDataSet4 = new GuiLibGraphDataSet;

    injectorTempProfilePageGraphCompleteProfileDataSet = new GuiLibGraphDataSet;
    injectorTempProfilePageGraphDataSet0 = new GuiLibGraphDataSet;
    injectorTempProfilePageGraphDataSet1 = new GuiLibGraphDataSet;
    injectorTempProfilePageGraphDataSet2 = new GuiLibGraphDataSet;
    injectorTempProfilePageGraphDataSet3 = new GuiLibGraphDataSet;
    injectorTempProfilePageGraphDataSet4 = new GuiLibGraphDataSet;

    gasFlowProfilePageGraphCompleteProfileDataSet = new GuiLibGraphDataSet;
    gasFlowProfilePageGraphDataSet0 = new GuiLibGraphDataSet;
    gasFlowProfilePageGraphDataSet1 = new GuiLibGraphDataSet;
    gasFlowProfilePageGraphDataSet2 = new GuiLibGraphDataSet;
    gasFlowProfilePageGraphDataSet3 = new GuiLibGraphDataSet;
    gasFlowProfilePageGraphDataSet4 = new GuiLibGraphDataSet;

    // These are used in both column page graphs (conventional and DH)
    columnTempProfilePageGraphCompleteProfileDataSet = new GuiLibGraphDataSet;
    columnTempProfilePageGraphDataSet0 = new GuiLibGraphDataSet;
    columnTempProfilePageGraphDataSet1 = new GuiLibGraphDataSet;
    columnTempProfilePageGraphDataSet2 = new GuiLibGraphDataSet;
    columnTempProfilePageGraphDataSet3 = new GuiLibGraphDataSet;
    columnTempProfilePageGraphDataSet4 = new GuiLibGraphDataSet;

    runningInjectorPageGraphCompleteProfileDataSet = new GuiLibGraphDataSet;
    runningInjectorPageGraphDataSet0 = new GuiLibGraphDataSet;
    runningInjectorPageGraphDataSet1 = new GuiLibGraphDataSet;
    runningInjectorPageGraphDataSet2 = new GuiLibGraphDataSet;
    runningInjectorPageGraphDataSet3 = new GuiLibGraphDataSet;
    runningInjectorPageGraphDataSet4 = new GuiLibGraphDataSet;
}


/*
   Set up the graph on the Column temperature profile page from the method currently set up in the GC.
   
   Note that we always display this data as if the 'current time' were at the start of the method.
*/
void GetGCStatusLoop::SetupColumnTempProfilePageGraphDataFromGC(void)
{
    // We do not display 'columnTempProfilePageGraphCompleteProfileDataSet' in the graph - 
    // we use it to generate the datasets that we will display.
    // Use seconds as the dataset time units if the method duration is less than five minutes,
    // otherwise use minutes.
    columnTempProfilePageGraphCompleteProfileDataSet->SetupFromColumnTemperatureRampValues(usbDevice, usbHostGC);
    
    float totalColumnMethodTime = columnTempProfilePageGraphCompleteProfileDataSet->GetTotalMethodTime();
    
    TimeUnit timeUnit = MINUTES;
    float barInterval = graphBarIntervalMinutes;
    if(totalColumnMethodTime < methodTimeUnitsThreshold) {
        timeUnit = SECONDS;
        barInterval = graphBarIntervalSeconds;
    }
        
    // We do not display this graph while the method is running - so for the purposes of displaying the datasets in the graph, 
    // we say that 'now' is at the start of the method.
    
    // Dataset 0 is a line representing the temperature profile of the run from 'now' to the finish
    columnTempProfilePageGraphCompleteProfileDataSet->MakePartialCopy(0, totalColumnMethodTime, columnTempProfilePageGraphDataSet0);
    
    // Dataset 1 is a bar chart representing the temperature profile of the run from 'now' to the finish -
    // make sure that we have a bar at the exact end of the profile
    columnTempProfilePageGraphCompleteProfileDataSet->MakeInterpolatedPartialCopyWithFinalPoint(0, totalColumnMethodTime, barInterval, columnTempProfilePageGraphDataSet1);
    
    // Data set 2 is a line representing the temperature profile from the start to 'now'
    columnTempProfilePageGraphDataSet2->ClearData();
    
    // Data set 3 is a bar chart representing the temperature profile of the run from the start to 'now'
    columnTempProfilePageGraphDataSet3->ClearData();
    
    // Data set 4 is a single dot at the current time and temperature
    columnTempProfilePageGraphDataSet4->ClearData();
#ifdef USING_DATASET_4_ON_NON_RUNNING_GRAPHS    
    GuiConst_INT32S startTemp = columnTempProfilePageGraphCompleteProfileDataSet->GetYCoordAtXCoord(0);
    columnTempProfilePageGraphDataSet4->AddDataPoint(0, startTemp);
#endif // USING_DATASET_4_ON_NON_RUNNING_GRAPHS
    
    SetupColumnTempProfilePageXAxisLabel(timeUnit);
}


/*
   Set up the graph on the Injector temperature profile page from the method currently set up in the GC.
   
   Note that we always display this data as if the 'current time' were at the start of the method.
   
   Note also that the column method dataset must be setup before this function is called - i.e.
   SetupColumnTempProfilePageGraphDataFromGC must be called before this function 
   *****************************************************************************
   
   If not, this function will do nothing.
   
*/
void GetGCStatusLoop::SetupInjectorTempProfilePageGraphDataFromGC(void)
{
    // We do not display 'injectorTempProfilePageGraphCompleteProfileDataSet' in the graph - 
    // we use it to generate the datasets that we will display.
    
#ifdef PTV_RAMPS_AVAILABLE
    float totalMethodTime;
#else
    // Before setting up the injector temperature profile, we need a column method to base it on
    float totalMethodTime = columnTempProfilePageGraphCompleteProfileDataSet->GetTotalMethodTime();
    if(totalMethodTime <= 0.0f) {
        // Method not set up
        return;
    }
#endif // PTV_RAMPS_AVAILABLE

    
#ifdef PTV_RAMPS_AVAILABLE
    injectorTempProfilePageGraphCompleteProfileDataSet->SetupFromPTVTemperatureRampValues(usbDevice, usbHostGC);
    totalMethodTime = injectorTempProfilePageGraphCompleteProfileDataSet->GetTotalMethodTime();
#else
    injectorTempProfilePageGraphCompleteProfileDataSet->SetupInjectorTemperatureProfileToMatchColumnMethod(usbDevice, usbHostGC, columnTempProfilePageGraphCompleteProfileDataSet);
#endif // PTV_RAMPS_AVAILABLE

    TimeUnit timeUnit = MINUTES;
    float barInterval = graphBarIntervalMinutes;
    if(totalMethodTime < methodTimeUnitsThreshold) {
        timeUnit = SECONDS;
        barInterval = graphBarIntervalSeconds;
    }
        
    
    // We do not display this graph while the method is running - so for the purposes of displaying the datasets in the graph, 
    // we say that 'now' is at the start of the method.
    
    // Dataset 0 is a line representing the temperature profile of the run from 'now' to the finish
    injectorTempProfilePageGraphCompleteProfileDataSet->MakePartialCopy(0, totalMethodTime, injectorTempProfilePageGraphDataSet0);
    
    // Dataset 1 is a bar chart representing the temperature profile of the run from 'now' to the finish
    injectorTempProfilePageGraphCompleteProfileDataSet->MakeInterpolatedPartialCopyWithFinalPoint(0, totalMethodTime, barInterval, injectorTempProfilePageGraphDataSet1);
    
    // Data set 2 is a line representing the temperature profile from the start to 'now'
    injectorTempProfilePageGraphDataSet2->ClearData();
    
    // Data set 3 is a bar chart representing the temperature profile of the run from the start to 'now'
    injectorTempProfilePageGraphDataSet3->ClearData();
    
    // Data set 4 is a single dot at the current time and temperature
    injectorTempProfilePageGraphDataSet4->ClearData();
#ifdef USING_DATASET_4_ON_NON_RUNNING_GRAPHS    
    float startTemp = injectorTempProfilePageGraphCompleteProfileDataSet->GetYCoordAtXCoord(0);
    injectorTempProfilePageGraphDataSet4->AddDataPoint(0, startTemp);
#endif // USING_DATASET_4_ON_NON_RUNNING_GRAPHS

    SetupInjectorTempProfilePageXAxisLabel(timeUnit);
}


/*
   Set up the graph on the Gas flow profile page from the method currently set up in the GC.
   
   Note that we always display this data as if the 'current time' were at the start of the method.
   
   Note also that the column method must be set up before calling this function.
*/
void GetGCStatusLoop::SetupGasFlowProfilePageGraphDataFromGC(void)
{
    // Before setting up the gas pressure profile, we need a column method to base it on
    float totalColumnMethodTime = columnTempProfilePageGraphCompleteProfileDataSet->GetTotalMethodTime();
    if(totalColumnMethodTime <= 0.0f) {
        // Column method not set up
        return;
    }
    
    // We do not display 'gasFlowProfilePageGraphCompleteProfileDataSet' in the graph - 
    // we use it to generate the datasets that we will display.
    gasFlowProfilePageGraphCompleteProfileDataSet->SetupGasPressureProfileWithTimingsFromColumnMethod(usbDevice, usbHostGC, columnTempProfilePageGraphCompleteProfileDataSet);
    
    float totalGasFlowMethodTime = gasFlowProfilePageGraphCompleteProfileDataSet->GetTotalMethodTime();
    
    TimeUnit timeUnit = MINUTES;
    float barInterval = graphBarIntervalMinutes;
    if(totalGasFlowMethodTime < methodTimeUnitsThreshold) {
        timeUnit = SECONDS;
        barInterval = graphBarIntervalSeconds;
    }
    
        
    // We do not display this graph while the method is running - so for the purposes of displaying the datasets in the graph, 
    // we say that 'now' is at the start of the method.
    
    // Dataset 0 is a line representing the pressure profile of the run from 'now' to the finish
    gasFlowProfilePageGraphCompleteProfileDataSet->MakePartialCopy(0, totalGasFlowMethodTime, gasFlowProfilePageGraphDataSet0);
    
    // Dataset 1 is a bar chart representing the pressure profile of the run from 'now' to the finish
    gasFlowProfilePageGraphCompleteProfileDataSet->MakeInterpolatedPartialCopyWithFinalPoint(0, totalGasFlowMethodTime, barInterval, gasFlowProfilePageGraphDataSet1);
    
    // Data set 2 is a line representing the pressure profile from the start to 'now'
    gasFlowProfilePageGraphDataSet2->ClearData();
    
    // Data set 3 is a bar chart representing the pressure profile of the run from the start to 'now'
    gasFlowProfilePageGraphDataSet3->ClearData();
    
    // Data set 4 is a single dot at the current time and pressure 
    gasFlowProfilePageGraphDataSet4->ClearData();
#ifdef USING_DATASET_4_ON_NON_RUNNING_GRAPHS    
    GuiConst_INT32S startTemp = gasFlowProfilePageGraphCompleteProfileDataSet->GetYCoordAtXCoord(0);
    gasFlowProfilePageGraphDataSet4->AddDataPoint(0, startTemp);
#endif // USING_DATASET_4_ON_NON_RUNNING_GRAPHS

    SetupGasFlowProfilePageXAxisLabel(timeUnit);
}


/*
    Set up the complete temperature profile dataset for the graph on the 'running column' page
    to match the method data currently set up in the GC.
    
    We assume that we will not have to do this again while the current method is running.
*/
void GetGCStatusLoop::SetupRunningColumnPageGraphCompleteProfileDataSetFromGC(void)
{
    // Use seconds as the dataset time units if the method duration is less than five minutes,
    // otherwise use minutes
    runningColumnPageGraphCompleteProfileDataSet->SetupFromColumnTemperatureRampValues(usbDevice, usbHostGC);
    
    runningColumnPageGraph->SetXAxisRange(0, (GuiConst_INT32S) runningColumnPageGraphCompleteProfileDataSet->GetTotalMethodTime());

    if(runningColumnPageGraphCompleteProfileDataSet->GetTotalMethodTime() < methodTimeUnitsThreshold) {
        strcpy(GuiVar_runningColumnPageXAxisLabel, "Time (seconds)");
    } else {
        strcpy(GuiVar_runningColumnPageXAxisLabel, "Time (minutes)");
    }
}

/*
    Update the partial datasets for the temperature profile graph on the 'running column' page
    (i.e. the datasets that split up the profile into the section from the start to 'now'
    and the section from 'now' to the end) to match (a) the complete profile obtained from the GC,
    and (b) the current time.
    
    Caller *must* have previously called the 'SetupRunningColumnPageGraphCompleteProfileDataSetFromGC' function.
    
    Returns true if the datasets have changed, false if not
*/
bool GetGCStatusLoop::SetupRunningColumnPageGraphPartialDataSetsToMatchCurrentRunTime(bool runHasCompleted)
{
    if(columnMethodFinished) {
        return false; // Our work here is done...
    }
    
    float currentColumnMethodRunTime;
    GetRunTime(&currentColumnMethodRunTime);

    
    float totalColumnMethodMethodTime = runningColumnPageGraphCompleteProfileDataSet->GetTotalMethodTime();
    
    float barInterval = graphBarIntervalMinutes;
    if(totalColumnMethodMethodTime < methodTimeUnitsThreshold) {
        barInterval = graphBarIntervalSeconds;
    } else {
        // We are using minutes - work in whole minutes, and show the number we have completed 
        // (i.e. do not round the number up)
        currentColumnMethodRunTime = (float)floor((double)currentColumnMethodRunTime);
    }
    
    if(runHasCompleted) {
        // Make sure we show this
        currentColumnMethodRunTime = totalColumnMethodMethodTime;
    }
    
#ifdef USING_DATASET_4 
    if((currentColumnMethodRunTime < (previousColumnMethodRunTime + barInterval)) && (!runHasCompleted)) { // Must show the run has completed
        // No need to update data sets (there will be no changes)
        return false;
    }
#endif // USING_DATASET_4 - else update the graph continuously
    // 'else'...
    previousColumnMethodRunTime = currentColumnMethodRunTime;

    // Stop adding points when the current method has 'expired'
    if(currentColumnMethodRunTime < totalColumnMethodMethodTime) {    
        // Dataset 0 is a line representing the temperature profile of the run from 'now' to the finish
        runningColumnPageGraphCompleteProfileDataSet->MakePartialCopy(currentColumnMethodRunTime, totalColumnMethodMethodTime, runningColumnPageGraphDataSet0);
            
        // Dataset 1 is a bar chart representing the temperature profile of the run from 'now' to the finish - 
        // make sure that we have a bar at the exact end of the profile
        runningColumnPageGraphCompleteProfileDataSet->MakeInterpolatedPartialCopyWithFinalPoint(currentColumnMethodRunTime + barInterval, totalColumnMethodMethodTime, barInterval, runningColumnPageGraphDataSet1);
        // 'currentTime + barInterval' to prevent overlap with dataset 3
                
        // Data set 2 is a line representing the temperature profile from the start to 'now'
        runningColumnPageGraphCompleteProfileDataSet->MakePartialCopy(0, currentColumnMethodRunTime, runningColumnPageGraphDataSet2);
        
        // Data set 3 is a bar chart representing the temperature profile of the run from the start to 'now'
        runningColumnPageGraphCompleteProfileDataSet->MakeInterpolatedPartialCopy(0, currentColumnMethodRunTime, barInterval, runningColumnPageGraphDataSet3);
        
#ifdef USING_DATASET_4 
        // Data set 4 is a single dot at the current time and temperature
        runningColumnPageGraphDataSet4->ClearData();
        GuiConst_INT32S currentTemp = runningColumnPageGraphCompleteProfileDataSet->GetYCoordAtXCoord(currentColumnMethodRunTime);
        runningColumnPageGraphDataSet4->AddDataPoint(currentColumnMethodRunTime, currentTemp);
#endif // USING_DATASET_4
    } else {
        
        columnMethodFinished = true;

        // Do not leave data 'lying around' in the 'now' to the finish datasets
        // after we have completed the method
        runningColumnPageGraphDataSet0->ClearData();
        runningColumnPageGraphDataSet1->ClearData();
                
        // Data set 2 is a line representing the temperature profile from the start to 'now'
        runningColumnPageGraphCompleteProfileDataSet->MakePartialCopy(0, totalColumnMethodMethodTime, runningColumnPageGraphDataSet2);
        
        // Data set 3 is a bar chart representing the temperature profile of the run from the start to 'now' - 
        // make sure that we have a bar at the exact end of the profile
        runningColumnPageGraphCompleteProfileDataSet->MakeInterpolatedPartialCopyWithFinalPoint(0, totalColumnMethodMethodTime, barInterval, runningColumnPageGraphDataSet3);
        
#ifdef USING_DATASET_4 
        // Data set 4 is a single dot at the current time and temperature
        runningColumnPageGraphDataSet4->ClearData();
        GuiConst_INT32S currentTemp = runningColumnPageGraphCompleteProfileDataSet->GetYCoordAtXCoord(totalColumnMethodMethodTime);
        runningColumnPageGraphDataSet4->AddDataPoint(totalColumnMethodMethodTime, currentTemp);
#endif // USING_DATASET_4
    }

    return true;
}

/*
    Set up the complete gas flow profile dataset for the graph on the 'running gas' page
    to match the method data currently set up in the GC.
    
    We assume that we will not have to do this again while the current method is running.
*/
void GetGCStatusLoop::SetupRunningGasPageGraphCompleteProfileDataSetFromGC(void)
{
    // Before setting up the gas pressure profile, we need a column method to base it on
    float totalColumnMethodTime = runningColumnPageGraphCompleteProfileDataSet->GetTotalMethodTime();
    if(totalColumnMethodTime <= 0.0f) {
        // Column method not set up
        return;
    }
    
    runningGasPageGraphCompleteProfileDataSet->SetupGasPressureProfileWithTimingsFromColumnMethod(usbDevice, usbHostGC, runningColumnPageGraphCompleteProfileDataSet);
        
    runningGasPageGraph->SetXAxisRange(0, (GuiConst_INT32S) runningGasPageGraphCompleteProfileDataSet->GetTotalMethodTime());
    
    if(runningGasPageGraphCompleteProfileDataSet->GetTotalMethodTime() < methodTimeUnitsThreshold) {
        strcpy(GuiVar_runningGasPageXAxisLabel, "Time (seconds)");
    } else {
        strcpy(GuiVar_runningGasPageXAxisLabel, "Time (minutes)");
    }
}

/*
    Update the partial datasets for the gas flow profile graph on the 'running gas' page
    (i.e. the datasets that split up the profile into the section from the start to 'now'
    and the section from 'now' to the end) to match (a) the complete profile obtained from the GC,
    and (b) the current time.
    
    Caller *must* have previously called the 'SetupRunningGasPageGraphCompleteProfileDataSetFromGC' function.
    
    Returns true if the datasets have changed, false if not
*/
bool GetGCStatusLoop::SetupRunningGasPageGraphPartialDataSetsToMatchCurrentRunTime(bool runHasCompleted)
{
    if(gasMethodFinished) {
        return false; // Our work here is done...
    }

    float currentGasMethodRunTime;
    GetRunTime(&currentGasMethodRunTime);

    float totalGasMethodTime = runningGasPageGraphCompleteProfileDataSet->GetTotalMethodTime();
    
    float barInterval = graphBarIntervalMinutes;
    if(totalGasMethodTime < methodTimeUnitsThreshold) {
        barInterval = graphBarIntervalSeconds;
    } else {
        // We are using minutes - work in whole minutes, and show the number we have completed 
        // (i.e. do not round the number up)
        currentGasMethodRunTime = (float)floor((double)currentGasMethodRunTime);
    }
    
    if(runHasCompleted) {
        // Make sure we show this
        currentGasMethodRunTime = totalGasMethodTime;
    }
        
#ifdef USING_DATASET_4 
    if((currentGasMethodRunTime < (previousGasMethodRunTime + barInterval)) && (!runHasCompleted)) { // Must show the run has completed
        // No need to update data sets (there will be no changes)
        return false;
    }
#endif // USING_DATASET_4 - else update the graph continuously
    // 'else'...
    previousGasMethodRunTime = currentGasMethodRunTime;

    // Stop adding points when the current method has 'expired'
    if(currentGasMethodRunTime < totalGasMethodTime) {
        // Dataset 0 is a line representing the flow profile of the run from 'now' to the finish
        runningGasPageGraphCompleteProfileDataSet->MakePartialCopy(currentGasMethodRunTime, totalGasMethodTime, runningGasPageGraphDataSet0);
            
        // Dataset 1 is a bar chart representing the flow profile of the run from 'now' to the finish -
        // make sure that we have a bar at the exact end of the profile
        runningGasPageGraphCompleteProfileDataSet->MakeInterpolatedPartialCopyWithFinalPoint(currentGasMethodRunTime + barInterval, totalGasMethodTime, barInterval, runningGasPageGraphDataSet1);
        // 'currentTime + barInterval' to prevent overlap with dataset 3
                
        // Data set 2 is a line representing the flow profile from the start to 'now'
        runningGasPageGraphCompleteProfileDataSet->MakePartialCopy(0, currentGasMethodRunTime, runningGasPageGraphDataSet2);
        
        // Data set 3 is a bar chart representing the flow profile from the start to 'now'
        runningGasPageGraphCompleteProfileDataSet->MakeInterpolatedPartialCopy(0, currentGasMethodRunTime, barInterval, runningGasPageGraphDataSet3);
        
#ifdef USING_DATASET_4 
        // Data set 4 is a single dot at the current time and flow rate
        runningGasPageGraphDataSet4->ClearData();
        GuiConst_INT32S currentFlowRate = runningGasPageGraphCompleteProfileDataSet->GetYCoordAtXCoord(currentGasMethodRunTime);
        runningGasPageGraphDataSet4->AddDataPoint(currentGasMethodRunTime, currentFlowRate);
#endif // USING_DATASET_4 
    } else {

        gasMethodFinished = true;

        // Do not leave data 'lying around' in the 'now' to the finish datasets
        // after we have completed the method
        runningGasPageGraphDataSet0->ClearData();
        runningGasPageGraphDataSet1->ClearData();
        
        // Data set 2 is a line representing the flow profile from the start to 'now'
        runningGasPageGraphCompleteProfileDataSet->MakePartialCopy(0, currentGasMethodRunTime, runningGasPageGraphDataSet2);
        
        // Data set 3 is a bar chart representing the flow profile from the start to 'now'- 
        // make sure that we have a bar at the exact end of the profile
        runningGasPageGraphCompleteProfileDataSet->MakeInterpolatedPartialCopyWithFinalPoint(0, currentGasMethodRunTime, barInterval, runningGasPageGraphDataSet3);
        
#ifdef USING_DATASET_4
        // Data set 4 is a single dot at the current time and flow rate
        runningGasPageGraphDataSet4->ClearData();
        GuiConst_INT32S currentFlowRate = runningGasPageGraphCompleteProfileDataSet->GetYCoordAtXCoord(currentGasMethodRunTime);
        runningGasPageGraphDataSet4->AddDataPoint(currentGasMethodRunTime, currentFlowRate);
#endif // USING_DATASET_4 
    }

    return true;
}
    
/*
    Set up the complete injector temperature profile dataset for the graph on the second injector status
    (while running the method) page, to match the method data currently set up in the GC.
    
    We assume that we will not have to do this again while the current method is running.
*/
void GetGCStatusLoop::SetupRunningInjectorPageGraphCompleteProfileDataSetFromGC(void)
{
#ifndef PTV_RAMPS_AVAILABLE
    // If we do not have PTV ramps, then before we can set up the injector profile, we need a column method to base it on
    if(runningColumnPageGraphCompleteProfileDataSet->GetTotalMethodTime() <= 0.0f) {
        // Column method not set up
        return;
    }
#endif // PTV_RAMPS_AVAILABLE
    
#ifdef PTV_RAMPS_AVAILABLE
    runningInjectorPageGraphCompleteProfileDataSet->SetupFromPTVTemperatureRampValues(usbDevice, usbHostGC);
#else
    runningInjectorPageGraphCompleteProfileDataSet->SetupInjectorTemperatureProfileToMatchColumnMethod(usbDevice, usbHostGC, runningColumnPageGraphCompleteProfileDataSet);
#endif // PTV_RAMPS_AVAILABLE
   
    runningInjectorPageGraph->SetXAxisRange(0, (GuiConst_INT32S) runningInjectorPageGraphCompleteProfileDataSet->GetTotalMethodTime());
    
    if(runningInjectorPageGraphCompleteProfileDataSet->GetTotalMethodTime() < methodTimeUnitsThreshold) {
        strcpy(GuiVar_runningInjectorPageXAxisLabel, "Time (seconds)");
    } else {
        strcpy(GuiVar_runningInjectorPageXAxisLabel, "Time (minutes)");
    }
}

/*
    Update the partial datasets for the injector temperature profile graph on the 'running injector' page
    (i.e. the datasets that split up the profile into the section from the start to 'now'
    and the section from 'now' to the end) to match (a) the complete profile obtained from the GC,
    and (b) the current time.
    
    Caller *must* have previously called the 'SetupRunningInjectorPageGraphCompleteProfileDataSetFromGC' function.
    
    Returns true if the datasets have changed, false if not
*/
bool GetGCStatusLoop::SetupRunningInjectorPageGraphPartialDataSetsToMatchCurrentRunTime(bool runHasCompleted)
{
    if(injectorMethodFinished) {
        return false; // Our work here is done...
    }

    float currentInjectorMethodRunTime;
    GetRunTime(&currentInjectorMethodRunTime);

    float totalInjectorMethodTime = runningInjectorPageGraphCompleteProfileDataSet->GetTotalMethodTime();
    
    float barInterval = graphBarIntervalMinutes;
    if(totalInjectorMethodTime < methodTimeUnitsThreshold) {
        barInterval = graphBarIntervalSeconds;
    } else {
        // We are using minutes - work in whole minutes, and show the number we have completed 
        // (i.e. do not round the number up) - but leave the value unchanged if the injector method has completed - 
        // otherwise we will omit the final point/bar if the total time is not a whole number of minutes
        if(currentInjectorMethodRunTime < totalInjectorMethodTime) {
            currentInjectorMethodRunTime = (float)floor((double)currentInjectorMethodRunTime);
        }
    }
    
    if(runHasCompleted) {
        // Make sure we show this - but be careful of the fact that the injector method time
        // is not necessarily equal to the column method time - and currently the run completes
        // at the end of the column method time
        if(totalInjectorMethodTime == runningColumnPageGraphCompleteProfileDataSet->GetTotalMethodTime()) {
            currentInjectorMethodRunTime = totalInjectorMethodTime;
        }
        // If the time (i.e. duration) of the injector method is less than the column method,
        // we should already have shown it as completed before the column method (and therefore 
        // the run as a whole) completes. If it is greater, then it will not have completed 
        // by the time the column method completes - and therefore we should not show it 
        // as complete at that point (and without the 'if' around the assignment above, 
        // that is what we will do)
    }
        
#ifdef USING_DATASET_4 
    if((currentInjectorMethodRunTime < (previousInjectorMethodRunTime + barInterval)) && (!runHasCompleted)) { // Must show the run has completed
        // No need to update data sets (there will be no changes)
        return false;
    }
#endif // USING_DATASET_4 - else update the graph continuously
    // 'else'...
    previousInjectorMethodRunTime = currentInjectorMethodRunTime;

    // Stop adding points when the current method has 'expired'
    if(currentInjectorMethodRunTime < totalInjectorMethodTime) {
        // Dataset 0 is a line representing the flow profile of the run from 'now' to the finish
        runningInjectorPageGraphCompleteProfileDataSet->MakePartialCopy(currentInjectorMethodRunTime, totalInjectorMethodTime, runningInjectorPageGraphDataSet0);
            
        // Dataset 1 is a bar chart representing the flow profile of the run from 'now' to the finish -
        // make sure that we have a bar at the exact end of the profile
        runningInjectorPageGraphCompleteProfileDataSet->MakeInterpolatedPartialCopyWithFinalPoint(currentInjectorMethodRunTime + barInterval, totalInjectorMethodTime, barInterval, runningInjectorPageGraphDataSet1);
        // 'currentTime + barInterval' to prevent overlap with dataset 3
                
        // Data set 2 is a line representing the flow profile from the start to 'now'
        runningInjectorPageGraphCompleteProfileDataSet->MakePartialCopy(0, currentInjectorMethodRunTime, runningInjectorPageGraphDataSet2);
        
        // Data set 3 is a bar chart representing the flow profile from the start to 'now'
        runningInjectorPageGraphCompleteProfileDataSet->MakeInterpolatedPartialCopy(0, currentInjectorMethodRunTime, barInterval, runningInjectorPageGraphDataSet3);
        
#ifdef USING_DATASET_4 
        // Data set 4 is a single dot at the current time and flow rate
        runningInjectorPageGraphDataSet4->ClearData();
        GuiConst_INT32S currentFlowRate = runningInjectorPageGraphCompleteProfileDataSet->GetYCoordAtXCoord(currentGasMethodRunTime);
        runningGasPageGraphDataSet4->AddDataPoint(currentGasMethodRunTime, currentFlowRate);
#endif // USING_DATASET_4 
    } else {

        injectorMethodFinished = true;

        // Remember that it is the *column* method that controls how long the GC runs, *not* the injector method -
        // and they do not have to have the same length (i.e. duration). Therefore, the GC may still be running 
        // after the injector method has finished - and we do not want to display spurious bars off the right hand end of our graph
        if(currentInjectorMethodRunTime > totalInjectorMethodTime) {
            currentInjectorMethodRunTime = totalInjectorMethodTime;
        }

        // Do not leave data 'lying around' in the 'now' to the finish datasets
        // after we have completed the method
        runningInjectorPageGraphDataSet0->ClearData();
        runningInjectorPageGraphDataSet1->ClearData();
        
        // Data set 2 is a line representing the flow profile from the start to 'now'
        runningInjectorPageGraphCompleteProfileDataSet->MakePartialCopy(0, currentInjectorMethodRunTime, runningInjectorPageGraphDataSet2);
        
        // Data set 3 is a bar chart representing the flow profile from the start to 'now'- 
        // make sure that we have a bar at the exact end of the profile
        runningInjectorPageGraphCompleteProfileDataSet->MakeInterpolatedPartialCopyWithFinalPoint(0, currentInjectorMethodRunTime, barInterval, runningInjectorPageGraphDataSet3);
        
#ifdef USING_DATASET_4
        // Data set 4 is a single dot at the current time and flow rate
        runningInjectorPageGraphDataSet4->ClearData();
        GuiConst_INT32S currentFlowRate = runningInjectorPageGraphCompleteProfileDataSet->GetYCoordAtXCoord(currentInjectorMethodRunTime);
        runningInjectorPageGraphDataSet4->AddDataPoint(currentInjectorMethodRunTime, currentFlowRate);
#endif // USING_DATASET_4 
    }

    return true;
}
    
/*
    Update all the 'complete running profile' datasets from the GC.
    
    We will want to do this, for example, when we start the GC running.
*/
void GetGCStatusLoop::UpdateGCMethodRunningProfiles(void)
{
    SetupRunningColumnPageGraphCompleteProfileDataSetFromGC();
    SetupRunningGasPageGraphCompleteProfileDataSetFromGC();
    SetupRunningInjectorPageGraphCompleteProfileDataSetFromGC();
}


/*
    Caller is telling us "the GC has started running".
*/
void GetGCStatusLoop::SetGCIsRunning(void)
{
    previousColumnMethodRunTime = -99.0f;
    columnMethodFinished = false;
    
    previousGasMethodRunTime = -99.0f;
    gasMethodFinished = false;
    
    previousInjectorMethodRunTime = -99.0f;
    injectorMethodFinished = false;
    
    realGCIsRunning = true;
    
    runWasAborted = false;
}

/*
    Caller is telling us "the GC has stopped running".
*/
void GetGCStatusLoop::ClearGCIsRunning(void)
{    
    realGCIsRunning = false;
}


/*
    Tell the caller whether or not we 'think' the GC is running
*/
bool GetGCStatusLoop::GetGCIsRunningFlag(void)
{    
    return realGCIsRunning;
}


/*
    Returns the current page selection to the caller.
*/
GuiConst_INT16U GetGCStatusLoop::GetCurrentPage(void)
{
    return currentPage;
}

/*
    Allows the caller to set the current page. As well as setting the page number,
    we may need to take other action - e.g. displaying the data for that page.
    
    Args: new page (easyGUI "structure") number 
    
    No return code.
*/
void GetGCStatusLoop::SetCurrentPage(GuiConst_INT16U newCurrentPage)
{
    if(currentPage != newCurrentPage) {
        currentPage = newCurrentPage;
        
        pageJustChanged = true; // Try this - can it prevent crashes on updating?
        
        needToUpdateProfileGraphs = true;
        
//#define IS_THIS_NECESSARY_NOW
#ifdef IS_THIS_NECESSARY_NOW
        // Stop the status rectangles flashing when we display these pages/structures
        if((currentPage != GuiStruct_HomePage_1) &&
           (currentPage != GuiStruct_ColumnPage1_2) &&
           (currentPage != GuiStruct_ColumnPage2_9) &&
           (currentPage != GuiStruct_ColumnMethodPage_Def) &&
           (currentPage != GuiStruct_ColumnTempProfilePage_60) &&
           (currentPage != GuiStruct_InjectorPage1_3) &&
           (currentPage != GuiStruct_InjectorTempProfilePage_25) &&
           (currentPage != GuiStruct_InjectorGasStatusPage_30) &&
           (currentPage != GuiStruct_InjectorConsumablesPage_20) &&
           (currentPage != GuiStruct_DetectorFIDPage_4) &&
           (currentPage != GuiStruct_DetectorECDPage_12) &&
           (currentPage != GuiStruct_DetectorFPDPage_14) &&
           (currentPage != GuiStruct_DetectorTCDPage_11) &&
           (currentPage != GuiStruct_DetectorNPDPage_28) &&
           (currentPage != GuiStruct_DetectorNonePage_31) &&
           (currentPage != GuiStruct_DetectorPIDPage_29) &&
           (currentPage != GuiStruct_DetectorSPDIDPage_30) &&
           (currentPage != GuiStruct_DetectorTXLPage_27) &&
           (currentPage != GuiStruct_GasProfilePage_15) &&
           (currentPage != GuiStruct_GasInformationPage_6) && 
           (currentPage != GuiStruct_GasCalibrationPage_Def) && 
           (currentPage != GuiStruct_GasBackPressureDACPage_Def) && 
           (currentPage != GuiStruct_GasChannelDACAndADCPage_Def) && 
           (currentPage != GuiStruct_RunningDetectorPage_27)) { // It also causes flickering if we display the 'Running Detector' page here
            
            DisplayCurrentPageData(true);
        }
#undef IS_THIS_NECESSARY_NOW
#endif // IS_THIS_NECESSARY_NOW
        // Instead of the above, this is all that is necessary -
        // without these calls, the graphs on these pages are not displayed
        // when the page first appears
        if(currentPage == GuiStruct_RunningColumnPage_25) {
            DisplayRunningColumnPageData(true, false);
        } else if (currentPage == GuiStruct_RunningGasPage_28) {
            DisplayRunningGasPageData(true, false);
        } else if (currentPage == GuiStruct_RunningInjectorProfilePage_Def) {
            DisplayRunningInjectorProfilePageData(true, false);
        }
    }
}

/*
    Allows the caller to tell us which component status colour areas to use for the home page.
    
    Args: pointer to the new component status colour areas for the home page
    
    No return code.
*/
void GetGCStatusLoop::SetHomePageGCComponentStatusColorAreas(HomePageGCComponentStatusColorAreas* newColorAreas)
{
    homePageGCComponentStatusColorAreas = newColorAreas;
    
    UpdateHomePageGCComponentStatusColorAreas();
}

/*
    Allows the caller to tell us which component status colour areas to use for the single component pages
    (e.g. column, detector, etc).
    
    Args: pointer to the new component status colour areas for the single component pages
    
    No return code.
*/
void GetGCStatusLoop::SetSingleGCComponentPageStatusColorAreas(SingleGCComponentPageStatusColorAreas* newColorAreas)
{
    singleGCComponentPageStatusColorAreas = newColorAreas;
    
    UpdateSingleGCComponentPageStatusColorArea(COLUMN);
    UpdateSingleGCComponentPageStatusColorArea(INJECTOR);
    UpdateSingleGCComponentPageStatusColorArea(DETECTOR);
    UpdateSingleGCComponentPageStatusColorArea(GAS);

}


/*
    Displays the specified text at the specified location, with black text on a white background.
    
    Args: pointer to the null-terminated string to display
          x coordinate
          y coordinate
          
    No return code.
*/
void GetGCStatusLoop::DisplayText(char *text, short X, short Y, GuiConst_INT8U alignment, GuiConst_INT16U fontNo, GuiConst_INTCOLOR foreColor)
{
    GuiLib_DrawStr(
        X,                      //GuiConst_INT16S X,
        Y,                      //GuiConst_INT16S Y,
        fontNo,                 //GuiConst_INT16U FontNo,
        text,                   //GuiConst_TEXT PrefixLocate *String,
        alignment,              //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,
        foreColor,              //GuiConst_INTCOLOR ForeColor,
        0xFFFF                  //GuiConst_INTCOLOR BackColor
    ); 
}

/*
    Sends a command (known as a 'report') to the GC, and returns the response.
    
    Args: pointer to (null-terminated) command to use
          pointer to buffer to contain the (also null-terminated) response
          
    No return code.
*/
void GetGCStatusLoop::SetGCDeviceReport(char *cmd, char *response)
{
#define USE_GC_UTILS // Testing new class
#ifdef USE_GC_UTILS
    USBHostGCUtilities::SendCommandToGCAndGetResponse(usbDevice, usbHostGC, cmd, response);
#else
    // Guard against simultaneous calls to usbHostGC->SetDeviceReport - 
    // it is not re-entrant (and nor is the GC)
    while(usbHostGC->ExecutingSetDeviceReport()) {}

    usbHostGC->SetDeviceReport(usbDevice, cmd, response);
#endif // USE_GC_UTILS
}
    
/*
    Executes a GC command that returns simply "DACK" if successful,
    "DNAK" or "EPKT" if failure.
    
    Args: a pointer to the command in question, as a null terminated string
    
    Returns true if the GC returned "DACK", false for anything else
*/
bool GetGCStatusLoop::ExecuteCommandWithDACKResponse(char *cmd)
{
#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, "EPKT" for error
    
#define DEBUG_HERE
#ifdef DEBUG_HERE
    char dbg[100];
    sprintf(dbg, "ECWDKR - %s returned %s", cmd, response);
    EasyGUIDebugPrint(dbg, 0, 15);   
#undef DEBUG_HERE
#endif
    
    return (response[1] == 'A');
#endif // USE_GC_UTILS
}

/*
    The commands to get the GC status ("QSTA") and its fault state ("QFLT") are very similar,
    in that they both return a response of the same form, with the (integer) status/fault value
    as the final two digits. This function handles both commands, returning the status/fault code
    as an integer.
    
    Args: a pointer to the null-terminated command string to be passed to the GC
         (note that this function does not check that this is either "QSTA" or "QFLT" -
          this is up to the caller)
              
    Obtains the status/fault code from the GC, and returns it as an integer. 
    NOTE: returns -1 if there is an error. This is *not* a valid GC status/fault code,
    and the caller must check for it.
*/
int GetGCStatusLoop::GetGCStatusOrFaultCode(char *cmd)
{
    char response[GC_MESSAGE_LENGTH+2];

    SetGCDeviceReport(cmd, response);

    int gcStatusCode;

    // We expect a response of the form "Dxxx00nn", where the two digits 'nn' are the status code

    // But check for "EPKT" first...
    if(response[0] == 'E') {
        gcStatusCode = -1; // *** Caller must check for this ***
    } else {
        sscanf(&response[6], "%d", &gcStatusCode);
    }
    
    return gcStatusCode;
}

/*
    Obtains the GC status, using the "QSTA" command.
    
    Returns the status to the caller.
    Note that this may be -1 if there was an error. Caller *must* check for this.
    Otherwise see the GC_STATE enumeration (GCStateAndFaultCodes.h)
    for the meaning of these codes.
*/
int GetGCStatusLoop::GetGCStatus(void)
{
    return GetGCStatusOrFaultCode("QSTA");
}

/*
    Obtains the GC fault state, using the "QFLT" command.
    
    Returns the fault state to the caller.
    Note that this may be -1 if there was an error - caller *must* check for this. 
    Otherwise see the GC_FAULT enumeration (GCStateAndFaultCodes.h)
    for the meaning of these codes.
*/
int GetGCStatusLoop::GetGCFaultCode(void)
{
    return GetGCStatusOrFaultCode("QFLT");
}

/*
    Given GC status and fault codes, returns the corresponding descriptive text
    as a null-terminated string, obtained from the GCStateAndFaultCodes class.
    
    Args are:   the state code
                the fault code
                a pointer to the buffer to contain the null-terminated string
                describing the GC state
                
    No return code.
*/
void GetGCStatusLoop::GetGCStateAsInfoString(int gcStateCode, int gcFaultCode, char *statusString)
{
    char buff[100];

#ifdef USE_VERSION_102 // See GCStateAndFaultCodes.h
    if(gcStateCode == GC_STATE_102_METHOD_FAULTED) {
#else
    if(gcStateCode == GC_STATE_FAULTED) {
#endif
        if(gcStateAndFaultCodes.GetFaultCodeString(gcFaultCode, buff)) {
            sprintf(statusString, "GC faulted: %s", buff);
        } else {
            sprintf(statusString, "GC faulted: unknown fault code %d", gcFaultCode);
        }

    } else {

        if(gcStateAndFaultCodes.GetStateCodeString(gcStateCode, buff)) {
            sprintf(statusString, "GC state: %s", buff);
        } else {
            sprintf(statusString, "GC state: unknown state code %d", gcStateCode);
        }
    }
}

/*
    Sets the easyGUI variable on the GCNotReadyToRun page
    to a string that tells the user why it is not ready to run
*/
void GetGCStatusLoop::SetupGCNotReadyStateEasyGUIVariable(void)
{
    int gcStatus = GetGCStatus();
    GCStateSimplified simplifiedGCState = GCStateOrFaultCode::GetSimplifiedGCState(gcStatus);
    gcStateAndFaultCodes.GetSimplifiedStateCodeString(simplifiedGCState, GuiVar_gcNotReadyState);
    
}


/*
    Tells the caller whether or not the GC is in a fault state. 
    If so, returns true, and copies a string describing the status to the specified buffer.
    If not, returns false (and copies nothing to the buffer).
    
    Args: the current GC status
          pointer to a buffer to contain the null-terminated string describing the status.
    
    Return code: true if the GC is in a fault state, false if not.
*/
bool GetGCStatusLoop::GCHasFaulted(int gcStatus, char* statusString)
{
    bool gcHasFaulted = false;   
    statusString[0] = '\0';
    
    if(gcStatus == -1) { // Got "EPKT" as response from GC
        strcpy(statusString, "Failed to get status");
        gcHasFaulted = true;
    } else {
#ifdef USE_VERSION_102 // See GCStateAndFaultCodes.h
        int gcFaultCode = GC_FAULT_102_NO_ERROR;
        if(gcStatus == GC_STATE_102_METHOD_FAULTED) {
            gcFaultCode = GetGCFaultCode();
            
            if(gcFaultCode != GC_FAULT_102_NO_ERROR) {
                gcHasFaulted = true;
            }
        }
#else
        int gcFaultCode = GC_FAULT_NO_ERROR;
        if(gcStatus == GC_STATE_FAULTED) {
            gcFaultCode = GetGCFaultCode();
            
            if(gcFaultCode != GC_FAULT_NO_ERROR) {
                gcHasFaulted = true;
            }
        }
#endif
        GetGCStateAsInfoString(gcStatus, gcFaultCode, statusString);
    }
    
    return gcHasFaulted;
}

/*
    Version of the above that does not require any arguments.

    Tells the caller whether or not the GC is in a fault state. 
    
    No arguments.
    
    Return code: true if the GC is in a fault state, false if not.
*/
bool GetGCStatusLoop::GCHasFaulted(void)
{
    char statusString[100];

    return GCHasFaulted(GetGCStatus(), statusString);
}


/*
    Get the temperature of a GC component (column, detector, etc), and returns it as a null-terminated string, with a descriptive prefix.
    The GC commands for all of the component temperatures give a similar response.
    
    Note also that this code is intended to be as efficient as possible.
    
    Args: pointer to the null-terminated string specifying the command to get the temperature
          pointer to the buffer to contain the temperature, also as a null-terminated string
          optional bool set true if the value is in units of one-tenth of a degree, 
          false if whole degrees (default is true)
          
    No return code.
*/
void GetGCStatusLoop::GetComponentTemperature(char *cmd, char *temp, bool wantPrefix, bool wantDegSuffix, bool oneTenthDegree)
{
    char response[50];
    SetGCDeviceReport(cmd, response);
    // We expect a response like this: "Dxxx1234" - temp in units of 0.1 deg

    int index = 0;
    
    if(wantPrefix) {
        temp[index++] = 'T';
        temp[index++] = 'e';
        temp[index++] = 'm';
        temp[index++] = 'p';
        temp[index++] = ':';
        temp[index++] = ' ';
    }
        
    // But check for "EPKT" first
    if(response[0] == 'E') {
        temp[index++] = '*';
        temp[index++] = '*';
        temp[index++] = ' ';
        temp[index++] = 'E';
        temp[index++] = 'r';
        temp[index++] = 'r';
        temp[index++] = 'o';
        temp[index++] = 'r';
        temp[index++] = ' ';
        temp[index++] = '*';
        temp[index++] = '*';
    } else {
        // Ignore leading zeroes
        bool wantNextChars = false;
        if(response[4] != '0') {
            temp[index++] = response[4];
            wantNextChars = true;
        }
        if(wantNextChars || (response[5] != '0')) {
            temp[index++] = response[5];
            wantNextChars = true;
        }
        // If the value is zero, make sure we return "0.0" - 
        // we just don't want any zeroes before that
        if(oneTenthDegree) {
            temp[index++] = response[6];
            temp[index++] = '.';
            temp[index++] = response[7];
        } else {
            if(wantNextChars || (response[6] != '0')) {
                temp[index++] = response[6];
            }
            temp[index++] = response[7];
        }
        if(wantDegSuffix) {
#define TRY_DEG_SYMBOL
#ifdef  TRY_DEG_SYMBOL
            temp[index++] = ' ';
            temp[index++] = degSymbol;
            temp[index++] = 'C';
#else
            temp[index++] = ' ';
            temp[index++] = 'd';
            temp[index++] = 'e';
            temp[index++] = 'g';
            temp[index++] = ' ';
            temp[index++] = 'C';
#endif //  TRY_DEG_SYMBOL
        }
    }

    temp[index++] = '\0';
}

/*
    Get the temperature of a GC component (column, detector, etc), and returns it as a floating-point value.
    The GC commands for all of the component temperatures give a similar response.
    
    Note also that this code is intended to be as efficient as possible.
    
    Args: pointer to the null-terminated string specifying the command to get the temperature
          pointer to the floating-point variable to contain the temperature
          optional bool set true if the value is in units of one-tenth of a degree, 
          false if whole degrees (default is true)
          
    No return code.
*/
void GetGCStatusLoop::GetComponentTemperature(char *cmd, float *temp, bool oneTenthDegree)
{
    char buff[10];
    char response[50];
    SetGCDeviceReport(cmd, response);
    // We expect a response like this: "Dxxx1234" - temp in units of 0.1 deg
    
    // But check for "EPKT" first
    if(response[0] == 'E') {
        *temp = -1.0f; // ** Caller must check for this **
    } else {
        buff[0] = response[4];
        buff[1] = response[5];
        buff[2] = response[6];
        if(oneTenthDegree) {
            buff[3] = '.';
            buff[4] = response[7];
        } else {
            buff[3] = response[7];
        }

        sscanf(buff, "%f", temp);
    }
//#define DEBUG_HERE
#ifdef DEBUG_HERE
    char dbg[100];
    sprintf(dbg, "GGCSL::GCT - returning : %f", *temp);
    EasyGUIDebugPrint(dbg, 0, 20);
#undef DEBUG_HERE
#endif
}

/*
    Gets the column temperature, returning it as a null-terminated string, with a descriptive prefix.
    
    Args: pointer to a buffer to contain the null-terminated string specifying the temperature.
          boolean true if the caller wants an identifying prefix, false if not
    
    No return code.
*/
void GetGCStatusLoop::GetColumnTemperature(char *temp, bool wantPrefix)
{
    GetComponentTemperature("QCOL", temp, wantPrefix, true);
}

/*
    Gets the column temperature, returning it as a null-terminated string.
    
    Args: pointer to a buffer to contain the null-terminated string specifying the temperature.
          boolean true if the caller wants an identifying prefix, false if not
          boolean true if the caller wants a suffix specifyng the units, false if not
    
    No return code.
*/
void GetGCStatusLoop::GetColumnTemperature(char *temp, bool wantPrefix, bool wantSuffix)
{
    GetComponentTemperature("QCOL", temp, wantPrefix, wantSuffix);
}

/*
    Gets the column temperature, returning it as a floating-point value.
    
    Args: pointer to a floating point variable to contain the temperature
    
    No return code.
*/
void GetGCStatusLoop::GetColumnTemperature(float *temp)
{
    GetComponentTemperature("QCOL", temp);
}

/*
    Gets the target column temperature, returning it as a null-terminated string

    Args: pointer to a buffer to contain the null-terminated string specifying the temperature.
          pointer to a string specifying the sprintf format to use
    No return code.
*/
void GetGCStatusLoop::GetColumnTargetTemperature(char *temp, const char *format)
{
    char buff[40];
    GetComponentTemperature("GCOL", buff, false, false, false); // Target temperature is in whole degrees, not one-tenth
    
    sprintf(temp, format, buff);
}

/*
    Gets the column temperature, returning it as a null-terminated string, with a descriptive prefix.
    
    Args: pointer to a buffer to contain the null-terminated string specifying the temperature.
          boolean true if the caller wants an identifying prefix, false if not
    
    No return code.
*/
void GetGCStatusLoop::GetDirectlyHeatedColumnTemperature(char *temp, bool wantPrefix)
{
    GetComponentTemperature("QDCT", temp, wantPrefix, true);
}

/*
    Gets the column temperature, returning it as a floating-point value.
    
    Args: pointer to a floating point variable to contain the temperature
    
    No return code.
*/
void GetGCStatusLoop::GetDirectlyHeatedColumnTemperature(float *temp)
{
    GetComponentTemperature("QDCT", temp);
}

/*
    Gets the detector temperature, returning it as a null-terminated string, with 'deg C' as suffix.
    
    Args: pointer to a buffer to contain the null-terminated string specifying the temperature.
    
    No return code.
*/
void GetGCStatusLoop::GetDetectorTemperature(char *temp)
{
    char buff[40];
    GetComponentTemperature("QDET", buff, true, true);

    // Temporary - omit "Temp: " prefix
    strcpy(temp, &buff[6]);
}

/*
    Gets the detector temperature, returning it as a floating-point value.
    
    Args: pointer to a floating point variable to contain the temperature
    
    No return code.
*/
void GetGCStatusLoop::GetDetectorTemperature(float *temp)
{
    GetComponentTemperature("QDET", temp);
}

/*
    Gets the target detector temperature, returning it as a null-terminated string

    Args: pointer to a buffer to contain the null-terminated string specifying the temperature.
          string specifying the sprintf format to use
          
    No return code.
*/
void GetGCStatusLoop::GetDetectorTargetTemperature(char *temp, const char *format)
{
    char buff[40];
    GetComponentTemperature("GDET", buff, false, false, false); // Target temperature is in whole degrees, not one-tenth
    
    sprintf(temp, format, buff);
}


/*
    Gets the filament polarity for a TCD detector, and returns it as a null-terminated string.
    
    Note also that this code is intended to be as efficient as possible.
    
    Args: pointer to the buffer to contain the polarity, as a null-terminated string
          
    No return code.
*/
void GetGCStatusLoop::GetTCDDetectorFilamentPolarity(char *polarity)
{
    char response[50];
    SetGCDeviceReport("GPOL", response);

    // We expect a response like this: "DPOL0001" for positive, "DPOL0002" for negative
    int index = 0;
    // But check for "EPKT" first
    if(response[0] == 'E') {
        polarity[index++] = '*';
        polarity[index++] = '*';
        polarity[index++] = ' ';
        polarity[index++] = 'E';
        polarity[index++] = 'r';
        polarity[index++] = 'r';
        polarity[index++] = 'o';
        polarity[index++] = 'r';
        polarity[index++] = ' ';
        polarity[index++] = '*';
        polarity[index++] = '*';
    } else {
        switch(response[7]) {
            case '1':
                polarity[index++] = 'p';
                polarity[index++] = 'o';
                polarity[index++] = 's';
                polarity[index++] = 'i';
                polarity[index++] = 't';
                polarity[index++] = 'i';
                polarity[index++] = 'v';
                polarity[index++] = 'e';
                break;
            case '2':
                polarity[index++] = 'n';
                polarity[index++] = 'e';
                polarity[index++] = 'g';
                polarity[index++] = 'a';
                polarity[index++] = 't';
                polarity[index++] = 'i';
                polarity[index++] = 'v';
                polarity[index++] = 'e';
                break;
            default:
                polarity[index++] = 'i';
                polarity[index++] = 'n';
                polarity[index++] = 'v';
                polarity[index++] = 'a';
                polarity[index++] = 'l';
                polarity[index++] = 'd';
                polarity[index++] = 'd';
                break;
        }
    }
    polarity[index] = '\0';
}

/*
    Gets the filament temperature for a TCD detector, and returns it as a null-terminated string.
    
    Note also that this code is intended to be as efficient as possible.
    
    Args: pointer to the buffer to contain the temperature, as a null-terminated string
          
    No return code.
*/
void GetGCStatusLoop::GetTCDDetectorFilamentTemperature(char *temp)
{
    char buff[40];
    GetComponentTemperature("GFIL", buff, true, true);

    // Temporary - omit "Temp: " prefix
    strcpy(temp, &buff[6]);
}

/*
    Gets the amplifier range (gain) for a TCD detector, and returns it as a null-terminated string.
    
    Note also that this code is intended to be as efficient as possible.
    
    Args: pointer to the buffer to contain the range, as a null-terminated string
          
    No return code.
*/
void GetGCStatusLoop::GetTCDDetectorRange(char *range)
{
    char response[50];
    SetGCDeviceReport("GRNG", response);

    // We expect a response like this: "DRNG0001" for x1, "DRNG0002" for x10
    int index = 0;
    // But check for "EPKT" first
    if(response[0] == 'E') {
        range[index++] = '*';
        range[index++] = '*';
        range[index++] = ' ';
        range[index++] = 'E';
        range[index++] = 'r';
        range[index++] = 'r';
        range[index++] = 'o';
        range[index++] = 'r';
        range[index++] = ' ';
        range[index++] = '*';
        range[index++] = '*';
    } else {
        switch(response[7]) {
            case '1':
                range[index++] = 'x';
                range[index++] = '1';
                break;
            case '2':
                range[index++] = 'x';
                range[index++] = '1';
                range[index++] = '0';
                break;
            default:
                range[index++] = 'i';
                range[index++] = 'n';
                range[index++] = 'v';
                range[index++] = 'a';
                range[index++] = 'l';
                range[index++] = 'd';
                range[index++] = 'd';
                break;
        }
    }
    range[index] = '\0';
}

/*
    Gets the current for an ECD detector, and returns it as a null-terminated string.
    
    Note also that this code is intended to be as efficient as possible.
    
    Args: pointer to the buffer to contain the current, as a null-terminated string
          
    No return code.
*/
void GetGCStatusLoop::GetECDDetectorCurrent(char *current)
{
    char response[50];
    SetGCDeviceReport("GCUR", response);

    // We expect a response like this: "DCURnnnn", where 'nnnn' is the sensitivity.
    // TODO: perform appropriate interpretation on the value 'nnnn'.
    //       Currently, we just return it unchanged
    
    int index = 0;
    // Check for "EPKT" first
    if(response[0] == 'E') {
        current[index++] = '*';
        current[index++] = '*';
        current[index++] = ' ';
        current[index++] = 'E';
        current[index++] = 'r';
        current[index++] = 'r';
        current[index++] = 'o';
        current[index++] = 'r';
        current[index++] = ' ';
        current[index++] = '*';
        current[index++] = '*';
    } else {
        // Ignore leading zeroes
        bool wantNextChars = false;
        if(response[4] != '0') {
            current[index++] = response[4];
            wantNextChars = true;
        }
        if(wantNextChars || (response[5] != '0')) {
            current[index++] = response[5];
            wantNextChars = true;
        }
        if(wantNextChars || (response[6] != '0')) {
            current[index++] = response[6];
        }        
        // If the value is zero, make sure we return "0" - 
        // we just don't want any zeroes before that
        current[index++] = response[7];
    }
    current[index] = '\0';
}

/*
    Gets the range for an FPD detector, and returns it as a null-terminated string.
    
    Note also that this code is intended to be as efficient as possible.
    
    Args: pointer to the buffer to contain the current, as a null-terminated string
          
    No return code.
    
    *** This detector type now seems to have the same "Get Range" command as all other types ***
    *** i.e. "GRNG" - so this function is not currently used                                 ***
*/
void GetGCStatusLoop::GetFPDDetectorRange(char *range)
{
    char response[50];
    SetGCDeviceReport("GRN2", response);

    // We expect a response like this: "DRNG0001" for x1, "DRNG0002" for x10, "DRNG0003" for x100
    int index = 0;
    // But check for "EPKT" first
    if(response[0] == 'E') {
        range[index++] = '*';
        range[index++] = '*';
        range[index++] = ' ';
        range[index++] = 'E';
        range[index++] = 'r';
        range[index++] = 'r';
        range[index++] = 'o';
        range[index++] = 'r';
        range[index++] = ' ';
        range[index++] = '*';
        range[index++] = '*';
    } else {
        switch(response[7]) {
            case '1':
                range[index++] = 'x';
                range[index++] = '1';
                break;
            case '2':
                range[index++] = 'x';
                range[index++] = '1';
                range[index++] = '0';
                break;
            case '3':
                range[index++] = 'x';
                range[index++] = '1';
                range[index++] = '0';
                range[index++] = '0';
                break;
            default:
                range[index++] = 'i';
                range[index++] = 'n';
                range[index++] = 'v';
                range[index++] = 'a';
                range[index++] = 'l';
                range[index++] = 'd';
                range[index++] = 'd';
                break;
        }
    }
    range[index] = '\0';
}

/*
    Gets the current for an ECD detector, and returns it as a null-terminated string.
    
    Note also that this code is intended to be as efficient as possible.
    
    Args: pointer to the buffer to contain the current, as a null-terminated string
          
    No return code.
*/
void GetGCStatusLoop::GetFPDDetectorSensitivity(char *sensitivity)
{
    char response[50];
    SetGCDeviceReport("GSEN", response);

    // We expect a response like this: "DSENnnnn", where 'nnnn' is the sensitivity.
    // TODO: perform appropriate interpretation on the value 'nnnn'.
    //       Currently, we just return it unchanged
    
    int index = 0;
    // Check for "EPKT" first
    if(response[0] == 'E') {
        sensitivity[index++] = '*';
        sensitivity[index++] = '*';
        sensitivity[index++] = ' ';
        sensitivity[index++] = 'E';
        sensitivity[index++] = 'r';
        sensitivity[index++] = 'r';
        sensitivity[index++] = 'o';
        sensitivity[index++] = 'r';
        sensitivity[index++] = ' ';
        sensitivity[index++] = '*';
        sensitivity[index++] = '*';
    } else {
        // Ignore leading zeroes
        bool wantNextChars = false;
        if(response[4] != '0') {
            sensitivity[index++] = response[4];
            wantNextChars = true;
        }
        if(wantNextChars || (response[5] != '0')) {
            sensitivity[index++] = response[5];
            wantNextChars = true;
        }
        if(wantNextChars || (response[6] != '0')) {
            sensitivity[index++] = response[6];
        }        
        // If the value is zero, make sure we return "0" - 
        // we just don't want any zeroes before that
        sensitivity[index++] = response[7];
    }
    sensitivity[index] = '\0';
}

/*
    Gets the injector temperature, returning it as a null-terminated string, with a descriptive prefix.
    
    Args: pointer to a buffer to contain the null-terminated string specifying the temperature.
    
    No return code.
*/
void GetGCStatusLoop::GetInjectorTemperature(char *temp, bool wantPrefix)
{
    GetComponentTemperature("QINJ", temp, wantPrefix, true);
}

/*
    Gets the injector temperature, returning it as a floating-point value.
    
    Args: pointer to a floating point variable to contain the temperature
    
    No return code.
*/
void GetGCStatusLoop::GetInjectorTemperature(float *temp)
{
    GetComponentTemperature("QINJ", temp);
}

/*
    Gets the target injector temperature, returning it as a null-terminated string

    Args: pointer to a buffer to contain the null-terminated string specifying the temperature.
          pointer to a string specifying the sprintf format string to use
          
    No return code.
*/
void GetGCStatusLoop::GetInjectorTargetTemperature(char *temp, const char *format)
{
    char buff[40];
    GetComponentTemperature("GINJ", buff, false, false, false); // Target temperature is in whole degrees, not one-tenth
    
    sprintf(temp, format, buff);
}

/*
    Gets a pressure value using the specified command, and returns it as a null-terminated string, with a descriptive prefix.
    
    Note also that this code is intended to be as efficient as possible.
    
    Args: pointer to the buffer to contain the pressure, as a null-terminated string
          
    No return code.
*/
void GetGCStatusLoop::GetPressure(char *cmd, char *press, bool wantPrefix, bool wantUnits)
{
    char response[50];
    SetGCDeviceReport(cmd, response);
    // We expect a response like this: "DPRS1234" - pressure in units of 0.1 psi

    int index = 0;
    
    if(wantPrefix) {
        press[index++] = 'P';
        press[index++] = 'r';
        press[index++] = 'e';
        press[index++] = 's';
        press[index++] = 's';
        press[index++] = 'u';
        press[index++] = 'r';
        press[index++] = 'e';
        press[index++] = ':';
        press[index++] = ' ';
    }
    
    // But check for "EPKT" first
    if(response[0] == 'E') {
        press[index++] = '*';
        press[index++] = '*';
        press[index++] = ' ';
        press[index++] = 'E';
        press[index++] = 'r';
        press[index++] = 'r';
        press[index++] = 'o';
        press[index++] = 'r';
        press[index++] = ' ';
        press[index++] = '*';
        press[index++] = '*';
    } else {    
        bool wantNextChars = false;
        if(response[4] != '0') {
            press[index++] = response[4];
            wantNextChars = true;
        }
        if(wantNextChars || (response[5] != '0')) {
            press[index++] = response[5];
        }
        // If the value is zero, make sure we return "0.0" - 
        // we just don't want any zeroes before that
        press[index++] = response[6];
        press[index++] = '.';
        press[index++] = response[7];
        
        if(wantUnits) {
            press[index++] = ' ';
            press[index++] = 'p';
            press[index++] = 's';
            press[index++] = 'i';
        }
    }

    press[index++] = '\0';
}

/*
    Gets the gas pressure, and returns it as a null-terminated string, with a descriptive prefix.
    
    Args: pointer to the buffer to contain the gas pressure, as a null-terminated string
          
    No return code.
*/
void GetGCStatusLoop::GetGasPressure(char *press, bool wantPrefix, bool wantUnits)
{
//    GetPressure("QPRS", press, wantPrefix, wantUnits);
// Use GPRS command, not QPRS - it gets the initial pressure specified as part of the current method, which is what we want
    GetPressure("GPRS", press, wantPrefix, wantUnits);
}

/*
    Gets the actual, current, gas pressure (as opposed to the initial pressure specified in the current method), 
    and returns it as a null-terminated string, with a descriptive prefix if required.
    
    Args: pointer to the buffer to contain the current gas pressure, as a null-terminated string
          
    No return code.
*/
void GetGCStatusLoop::GetCurrentGasPressure(char *press, bool wantPrefix, bool wantUnits)
{
    GetPressure("QPRS", press, wantPrefix, wantUnits); // QPRS - get current pressure - not GPRS
}

/*
    Gets the pulsed pressure, and returns it as a null-terminated string, with a descriptive prefix.
    
    Args: pointer to the buffer to contain the pulsed pressure, as a null-terminated string
          
    No return code.
*/
void GetGCStatusLoop::GetGasPulsedPressure(char *pulsedPress)
{
    GetPressure("GPPS", pulsedPress, false, true);
}

/*
    Gets the gas pressure, returning it as a floating-point value.
    
    Args: pointer to a floating point variable to be set to the pressure
    
    No return code.
*/
void GetGCStatusLoop::GetGasPressure(float *press)
{
    char buff[100];
    char response[50];
//    SetGCDeviceReport("QPRS", response);
// Use GPRS command, not QPRS - it gets the initial pressure specified as part of the current method, which is what we want
    SetGCDeviceReport("GPRS", response);
    // We expect a response like this: "DPRS1234" - pressure in units of 0.1 psi

    // Allow for "EPKT" being returned from GC
    if(response[0] == 'E') {
        *press = -1.0f; // ** Caller must check for this **
    } else {
        buff[0] = response[4];
        buff[1] = response[5];
        buff[2] = response[6];
        buff[3] = '.';
        buff[4] = response[7];
        
        sscanf(buff, "%f", press);
    }
//#define DEBUG_HERE
#ifdef DEBUG_HERE
    char dbg[100];
    sprintf(dbg, "GGCSL::GGP - returning : %f", *press);
    EasyGUIDebugPrint(dbg, 0, 20);
#endif
//#undef DEBUG_HERE
}

/*
    Redraw one easyGUI variable on its component page.

    (Trying to reduce "display flickering" by not redrawing the entire page unnecessarily).
    
    Note that the font must match the one specified in the easyGUI project for this variable - 
    we cannot obtain this at runtime.
*/
void GetGCStatusLoop::RedrawSingleEasyGUIVariableOnComponentPage(GuiConst_INT16S X, GuiConst_INT16S Y, void *varPtr, GuiConst_INT8U alignment, GCComponent gcComponent)
{
    GuiLib_DrawVar(
       X, // GuiConst_INT16S X,
       Y, // GuiConst_INT16S Y,
       GuiFont_Helv20Bold, // GuiConst_INT16U FontNo,
       varPtr, // void PrefixLocate *VarPtr,
       GuiLib_VAR_STRING, // GuiConst_INT8U VarType,
       GuiLib_FORMAT_DEC, // GuiConst_INT8U FormatterFormat,
       10, // GuiConst_INT8U FormatterFieldWidth,
       GuiLib_FORMAT_ALIGNMENT_RIGHT, // GuiConst_INT8U FormatterAlignment,
       0, // GuiConst_INT8U FormatterDecimals,
       0, // GuiConst_INT8U FormatterShowSign,
       0, // GuiConst_INT8U FormatterZeroPadding,
       0, // GuiConst_INT8U FormatterTrailingZeros,
       0, // GuiConst_INT8U FormatterThousandsSeparator,
       alignment, // GuiConst_INT8U Alignment,
       GuiLib_PS_ON, // GuiConst_INT8U PsWriting,
       GuiLib_TRANSPARENT_OFF, // GuiConst_INT8U Transparent,
       GuiLib_UNDERLINE_OFF, // GuiConst_INT8U Underlining,
       200, // GuiConst_INT16S BackBoxSizeX,
       0, // GuiConst_INT16S BackBoxSizeY1,
       0, // GuiConst_INT16S BackBoxSizeY2,
       GuiLib_BBP_NONE, // GuiConst_INT8U BackBorderPixels,
       0, // GuiConst_INTCOLOR ForeColor, [Black]
       homePageGCComponentStatusColorAreas->GetComponentCurrentColor(gcComponent) // GuiConst_INTCOLOR BackColor [same as status rectangle]
       ); 
}

/*
    Redraw one easyGUI variable on the home page. Unlike the single component pages (as of 13 Mar 2017),
    we no longer have component status rectangles on the Home page.

    (Trying to reduce "display flickering" by not redrawing the entire page unnecessarily).
    
    Note that the font must match the one specified in the easyGUI project for this variable - 
    we cannot obtain this at runtime.
*/
void GetGCStatusLoop::RedrawSingleEasyGUIVariableOnHomePage(GuiConst_INT16S X, GuiConst_INT16S Y, void *varPtr, GuiConst_INT8U alignment)
{
    GuiLib_DrawVar(
       X, // GuiConst_INT16S X,
       Y, // GuiConst_INT16S Y,
       GuiFont_Helv20Bold, // GuiConst_INT16U FontNo,
       varPtr, // void PrefixLocate *VarPtr,
       GuiLib_VAR_STRING, // GuiConst_INT8U VarType,
       GuiLib_FORMAT_DEC, // GuiConst_INT8U FormatterFormat,
       10, // GuiConst_INT8U FormatterFieldWidth,
       GuiLib_FORMAT_ALIGNMENT_RIGHT, // GuiConst_INT8U FormatterAlignment,
       0, // GuiConst_INT8U FormatterDecimals,
       0, // GuiConst_INT8U FormatterShowSign,
       0, // GuiConst_INT8U FormatterZeroPadding,
       0, // GuiConst_INT8U FormatterTrailingZeros,
       0, // GuiConst_INT8U FormatterThousandsSeparator,
       alignment, // GuiConst_INT8U Alignment,
       GuiLib_PS_ON, // GuiConst_INT8U PsWriting,
       GuiLib_TRANSPARENT_ON, // GuiConst_INT8U Transparent,
       GuiLib_UNDERLINE_OFF, // GuiConst_INT8U Underlining,
       200, // GuiConst_INT16S BackBoxSizeX,
       0, // GuiConst_INT16S BackBoxSizeY1,
       0, // GuiConst_INT16S BackBoxSizeY2,
       GuiLib_BBP_NONE, // GuiConst_INT8U BackBorderPixels,
       0, // GuiConst_INTCOLOR ForeColor, [Black]
       0xFFFF // GuiConst_INTCOLOR BackColor [White]
       ); 
}

/*
    If the column temperature has changed on the home page,
    redraw it manually - do not redisplay the entire page just for this
    (trying to reduce "flashing and banging")
*/
void GetGCStatusLoop::DrawColumnTemperatureVariableOnHomePage(void)
{
    // Hard coded values taken from easyGUI
//    RedrawSingleEasyGUIVariableOnComponentPage(180, 140, &GuiVar_columnTemperature2, GuiLib_ALIGN_CENTER, COLUMN);
    RedrawSingleEasyGUIVariableOnHomePage(260, 100, &GuiVar_columnTemperature2, GuiLib_ALIGN_CENTER);
}

/*
    If the column target temperature has changed on the home page,
    redraw it manually - do not redisplay the entire page just for this
    (trying to reduce "flashing and banging")
*/
void GetGCStatusLoop::DrawColumnTargetTemperatureVariableOnHomePage(void)
{
    // Hard coded values taken from easyGUI
    RedrawSingleEasyGUIVariableOnHomePage(260, 140, &GuiVar_columnTargetTemperature, GuiLib_ALIGN_CENTER);
}

/*
    If the injector temperature has changed on the home page,
    redraw it manually - do not redisplay the entire page just for this
    (trying to reduce "flashing and banging")
*/
void GetGCStatusLoop::DrawInjectorTemperatureVariableOnHomePage(void)
{
    // Hard coded values taken from easyGUI
//    RedrawSingleEasyGUIVariableOnComponentPage(590, 140, &GuiVar_injectorTemperature2, GuiLib_ALIGN_CENTER, INJECTOR);
    RedrawSingleEasyGUIVariableOnHomePage(260, 300, &GuiVar_injectorTemperature2, GuiLib_ALIGN_CENTER);
}

/*
    If the injector target temperature has changed on the home page,
    redraw it manually - do not redisplay the entire page just for this
    (trying to reduce "flashing and banging")
*/
void GetGCStatusLoop::DrawInjectorTargetTemperatureVariableOnHomePage(void)
{
    // Hard coded values taken from easyGUI
    RedrawSingleEasyGUIVariableOnHomePage(260, 340, &GuiVar_injectorTargetTemperature, GuiLib_ALIGN_CENTER);
}

/*
    If the detector temperature has changed on the home page,
    redraw it manually - do not redisplay the entire page just for this
    (trying to reduce "flashing and banging")
*/
void GetGCStatusLoop::DrawDetectorTemperatureVariableOnHomePage(void)
{
    // Hard coded values taken from easyGUI
//    RedrawSingleEasyGUIVariableOnComponentPage(190, 340, &GuiVar_detectorTemperature2, GuiLib_ALIGN_LEFT, DETECTOR);
    RedrawSingleEasyGUIVariableOnHomePage(520, 100, &GuiVar_detectorTemperature2, GuiLib_ALIGN_LEFT);
}

/*
    If the detector target temperature has changed on the home page,
    redraw it manually - do not redisplay the entire page just for this
    (trying to reduce "flashing and banging")
*/
void GetGCStatusLoop::DrawDetectorTargetTemperatureVariableOnHomePage(void)
{
    // Hard coded values taken from easyGUI
    RedrawSingleEasyGUIVariableOnHomePage(520, 140, &GuiVar_detectorTargetTemperature, GuiLib_ALIGN_LEFT);
}

/*
    If the gas pressure has changed on the home page,
    redraw it manually - do not redisplay the entire page just for this
    (trying to reduce "flashing and banging")
*/
void GetGCStatusLoop::DrawGasPressureVariableOnHomePage(void)
{
    // Hard coded values taken from easyGUI
//    RedrawSingleEasyGUIVariableOnComponentPage(590, 340, &GuiVar_gasPressure2, GuiLib_ALIGN_CENTER, GAS);
    RedrawSingleEasyGUIVariableOnHomePage(520, 300, &GuiVar_gasPressure2, GuiLib_ALIGN_CENTER);
}

/*
    If the gas target pressures have changed on the home page,
    redraw it manually - do not redisplay the entire page just for this
    (trying to reduce "flashing and banging")
*/
void GetGCStatusLoop::DrawGasTargetPressureVariableOnHomePage(void)
{
    // Hard coded values taken from easyGUI
    RedrawSingleEasyGUIVariableOnHomePage(520, 340, &GuiVar_gasTargetPressure2, GuiLib_ALIGN_CENTER);
}

/*
    If the gas target pressures have changed on the home page,
    redraw it manually - do not redisplay the entire page just for this
    (trying to reduce "flashing and banging")
*/
void GetGCStatusLoop::DrawGCRealTimeClockVariableOnHomePage(void)
{
    // Hard coded values taken from easyGUI
    
    // Need to redraw background, to erase previous value of variable.
    // I 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 variable actually is.
    //(See also DrawBackgroundBitmapOverDoorLockAndReleaseButtons() in main.cpp)
    GuiLib_ShowBitmapArea(GuiStruct_Bitmap_BlankBackground, 0, 0, 230, 428, 580, 460, -1); // -1 means 'no transparent colour'
    
    RedrawSingleEasyGUIVariableOnHomePage(404, 450, &GuiVar_gcTimeOnHomePage, GuiLib_ALIGN_CENTER);
}

/*
    We display both the gas initial pressure and pulsed pressure on the Home page,
    in the same string. This function generates that string, in a consistent format.
    
    Args: a pointer to the string to be updated
    
    No return code
*/
void GetGCStatusLoop::UpdateGasTargetPressuresOnHomePage(char *buffer)
{
    char initialPressure[20];
    char pulsedPressure[20];
    
    GetPressure("GPRS", initialPressure, false, false);
    GetPressure("GPPS", pulsedPressure, false, false);

    sprintf(buffer, "(Target: %s/%s)", initialPressure, pulsedPressure);
}

/*
    Displays the data on the home page, by copying it to the relevant easyGUI variables,
    and calling the relevant functions to actually display it.
    
    Args: a boolean specifying whether or not the display is actually to be updated.
          Even if it is false when we are called, we may set it true if we discover
          one or more data items has changed - but note that we will not set it false 
          if it is already true. If it is true after we have looked at all 
          the home page data, we will then update the display. (If we are called
          with this value set to false, the caller is effectively saying
          'display the home page data only if it has changed'.)
          
    No return code.
*/
void GetGCStatusLoop::DisplayHomePageData(bool mustUpdateDisplay)
{
//    EasyGUIDebugPrint("Home Page", 100, 100);

#define DRAW_VARIABLES_INDIVIDUALLY
    char buff[40];
    
    if(GetColumnType() == DIRECTLY_HEATED_COLUMN) {
        GetDirectlyHeatedColumnTemperature(buff, false);
    } else {
        GetColumnTemperature(buff, false);
    }
    if(strcmp(buff, GuiVar_columnTemperature2) != 0) {
        strcpy(GuiVar_columnTemperature2, buff);
#ifdef DRAW_VARIABLES_INDIVIDUALLY
        DrawColumnTemperatureVariableOnHomePage();
#else
        mustUpdateDisplay = true;
#endif
    }
    
    GetColumnTargetTemperature(buff, "(Target: %s)");
    if(strcmp(buff, GuiVar_columnTargetTemperature) != 0) {
        strcpy(GuiVar_columnTargetTemperature, buff);
#ifdef DRAW_VARIABLES_INDIVIDUALLY
        DrawColumnTargetTemperatureVariableOnHomePage();
#else
        mustUpdateDisplay = true;
#endif
    }

    GetInjectorTemperature(buff, false);
    if(strcmp(buff, GuiVar_injectorTemperature2) != 0) {
        strcpy(GuiVar_injectorTemperature2, buff);
#ifdef DRAW_VARIABLES_INDIVIDUALLY
        DrawInjectorTemperatureVariableOnHomePage();
#else
        mustUpdateDisplay = true;
#endif
    }

    GetInjectorTargetTemperature(buff, "(Target: %s)");
    if(strcmp(buff, GuiVar_injectorTargetTemperature) != 0) {
        strcpy(GuiVar_injectorTargetTemperature, buff);
#ifdef DRAW_VARIABLES_INDIVIDUALLY
        DrawInjectorTargetTemperatureVariableOnHomePage();
#else
        mustUpdateDisplay = true;
#endif
    }

    GetDetectorTemperature(buff);
    if(strcmp(buff, GuiVar_detectorTemperature2) != 0) {
        strcpy(GuiVar_detectorTemperature2, buff);
#ifdef DRAW_VARIABLES_INDIVIDUALLY
        DrawDetectorTemperatureVariableOnHomePage();
#else
        mustUpdateDisplay = true;
#endif
    }

    GetDetectorTargetTemperature(buff, "(Target: %s)");
    if(strcmp(buff, GuiVar_detectorTargetTemperature) != 0) {
        strcpy(GuiVar_detectorTargetTemperature, buff);
#ifdef DRAW_VARIABLES_INDIVIDUALLY
        DrawDetectorTargetTemperatureVariableOnHomePage();
#else
        mustUpdateDisplay = true;
#endif
    }

    GetCurrentGasPressure(buff, false, true);
    if(strcmp(buff, GuiVar_gasPressure2) != 0) {
        strcpy(GuiVar_gasPressure2, buff);
#ifdef DRAW_VARIABLES_INDIVIDUALLY
        DrawGasPressureVariableOnHomePage();
#else
        mustUpdateDisplay = true;
#endif
    }
    
    UpdateGasTargetPressuresOnHomePage(buff);
    if(strcmp(buff, GuiVar_gasTargetPressure2) != 0) {
        strcpy(GuiVar_gasTargetPressure2, buff);
#ifdef DRAW_VARIABLES_INDIVIDUALLY
        DrawGasTargetPressureVariableOnHomePage();
#else
        mustUpdateDisplay = true;
#endif
    }

    GetGCRealTimeClockTime(buff);
    if(strcmp(buff, GuiVar_gcTimeOnHomePage) != 0) {
        strcpy(GuiVar_gcTimeOnHomePage, buff);
#ifdef DRAW_VARIABLES_INDIVIDUALLY
        DrawGCRealTimeClockVariableOnHomePage();
#else
        mustUpdateDisplay = true;
#endif
    }
    
    if(HomePageGCComponentStatusesHaveChanged()) {        
        mustUpdateDisplay = true;
    }
    
    if(mustUpdateDisplay) {

//#define WANT_STATUS_RECTANGLES_ON_HOME_PAGE // else use QSPIBitmaps
#ifdef WANT_STATUS_RECTANGLES_ON_HOME_PAGE
        // Updating the color areas involves getting the component statuses from the GC -
        // do this before GuiLib_Clear, otherwise the display stays blank for an annoyingly long time
        if(homePageGCComponentStatusColorAreas != NULL) {
            UpdateHomePageGCComponentStatusColorAreas();
        }
#endif // WANT_STATUS_RECTANGLES_ON_HOME_PAGE

        // Also get the GC state before GuiLib_Clear, for the same reason
        int gcStatus = GetGCStatus();
        GCStateSimplified simplifiedGCState = GCStateOrFaultCode::GetSimplifiedGCState(gcStatus);

        // Makes the display flicker - but omitting it means old text is not cleared from the display
#define WANT_GUILIB_CLEAR
#ifdef WANT_GUILIB_CLEAR
#ifdef USING_BACKGROUND_BITMAP
        DrawBackgroundBitmap(); // 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
#else
        GuiLib_Clear();
#endif
#undef WANT_GUILIB_CLEAR
#endif
        //...except that redrawing the status rectangles effectively clears the text on top of the rectangles -
        // so we do not need GuiLib_Clear here, since all the 'home page data' appears on top of the rectangles. 
        // Without it, only the text flickers, not the rectangles
        

#ifdef WANT_STATUS_RECTANGLES_ON_HOME_PAGE
        // 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.
        // (But note that we get the component statuses before GuiLib_Clear above.)
        if(homePageGCComponentStatusColorAreas != NULL) {
            homePageGCComponentStatusColorAreas->DisplayAll();

            lastColumnStatusDisplayedOnHomePage = homePageGCComponentStatusColorAreas->GetGCComponentStatus(COLUMN);
            lastInjectorStatusDisplayedOnHomePage = homePageGCComponentStatusColorAreas->GetGCComponentStatus(INJECTOR);
            lastDetectorStatusDisplayedOnHomePage = homePageGCComponentStatusColorAreas->GetGCComponentStatus(DETECTOR);
            lastGasStatusDisplayedOnHomePage = homePageGCComponentStatusColorAreas->GetGCComponentStatus(GAS);
        }
#else
        if(qspiBitmaps != NULL) {
            qspiBitmaps->DisplayAllHomePageBitmaps();
        }
#endif // WANT_STATUS_RECTANGLES_ON_HOME_PAGE

        GuiLib_ShowScreen(GuiStruct_HomePage_1, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);
        
        // ...but we draw the Run button and the Heat On/Off button on top of the structure
//#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
        DrawHeatOnOffButton();
        
        GCStateOrFaultCode::DrawSimplifiedStateMessageOnHomePageRunButton(simplifiedGCState); // *After* drawing the Run button

        // This variable (which sets the colour for the door actuator lock/unlock button text)
        // is not used on this page, only on the column pages - but this is the page
        // on which we turn the heat on and off. Try and make sure this variable is up to date
        // before we display the column pages, by updating it here
        SetupDoorActuatorCommandColour(usbDevice, usbHostGC, false);
        
        
        GuiLib_Refresh();    

#define DEBUG_HERE
#ifdef DEBUG_HERE
        static int counter = 0;
        char dbg[100];
        sprintf(dbg, "After GuiLib_Clear 1 [%d]", ++counter);
        EasyGUIDebugPrint(dbg, 0, 20);
#undef DEBUG_HERE
#endif
    }
}

/*
    Gets the maximum column temperature, and returns it as a null-terminated string, with a descriptive prefix.
    
    Note also that this code is intended to be as efficient as possible.
    
    Args: pointer to the buffer to contain the maximum temperature, as a null-terminated string
          boolean set true if the caller wants a full prefix ("Column max temp: "), 
          or false for a short prefix ("Max temp: ")
          boolean set false if the caller wants no prefix whatsoever
          
    No return code.
*/
void GetGCStatusLoop::GetColumnMaxTemperature(char *maxTemp, bool wantFullPrefix, bool wantAnyPrefix, bool wantUnits)
{
    char response[50];
//    SetGCDeviceReport("QCMX", response);
//  Correct command for this is now:
    SetGCDeviceReport("GCMX", response);
    // This returns a value in whole degrees, not 0.1 degree units
    // We expect a response like this: "DCMX1234" - where "1234" is the max temp 
    int index = 0;
    if(wantAnyPrefix) {
        if(wantFullPrefix) {
            maxTemp[index++] = 'C';
            maxTemp[index++] = 'o';
            maxTemp[index++] = 'l';
            maxTemp[index++] = 'u';
            maxTemp[index++] = 'm';
            maxTemp[index++] = 'n';
            maxTemp[index++] = ' ';
            maxTemp[index++] = 'm';
        } else {
            maxTemp[index++] = 'M';
        }
        maxTemp[index++] = 'a';
        maxTemp[index++] = 'x';
        maxTemp[index++] = ' ';
        maxTemp[index++] = 't';
        maxTemp[index++] = 'e';
        maxTemp[index++] = 'm';
        maxTemp[index++] = 'p';
        maxTemp[index++] = ':';
        maxTemp[index++] = ' ';
    }
    
    // But check for "EPKT" first
    if(response[0] == 'E') {
        maxTemp[index++] = '*';
        maxTemp[index++] = '*';
        maxTemp[index++] = ' ';
        maxTemp[index++] = 'E';
        maxTemp[index++] = 'r';
        maxTemp[index++] = 'r';
        maxTemp[index++] = 'o';
        maxTemp[index++] = 'r';
        maxTemp[index++] = ' ';
        maxTemp[index++] = '*';
        maxTemp[index++] = '*';
    } else {
        bool wantNextChars = false;
        if(response[4] != '0') {
            maxTemp[index++] = response[4];
            wantNextChars = true;
        }
        if(wantNextChars || (response[5] != '0')) {
            maxTemp[index++] = response[5];
            wantNextChars = true;
        }
        if(wantNextChars || (response[6] != '0')) {
            maxTemp[index++] = response[6];
        }
        // If the value is zero, make sure we return "0" - 
        // we just don't want any zeroes before that
        maxTemp[index++] = response[7];
        
        if(wantUnits) {
#ifdef TRY_DEG_SYMBOL
            maxTemp[index++] = ' ';
            maxTemp[index++] = degSymbol;
            maxTemp[index++] = 'C';
#else
            maxTemp[index++] = ' ';
            maxTemp[index++] = 'd';
            maxTemp[index++] = 'e';
            maxTemp[index++] = 'g';
            maxTemp[index++] = ' ';
            maxTemp[index++] = 'C';
#endif // TRY_DEG_SYMBOL
        }
    }

    maxTemp[index] = '\0';    
}

/*
    Gets the maximum column temperature, returning it as a floating-point value.
    
    Args: pointer to a floating point variable to be set to the maximum temperature
    
    No return code.
*/
void GetGCStatusLoop::GetColumnMaxTemperature(float *maxTemp)
{
    char response[50];
    SetGCDeviceReport("GCMX", response);

    // Check for "EPKT" first
    if(response[0] == 'E') {
        *maxTemp = 0.0;
    } else {
        sscanf(&response[4], "%f", maxTemp);
    }
    
//#define DEBUG_HERE
#ifdef DEBUG_HERE
    char dbg[100];
    sprintf(dbg, "GGCSL::GCMTT - returning : %f", *maxTemp);
    EasyGUIDebugPrint(dbg, 0, 20);
#undef DEBUG_HERE
#endif
}

/*
    Gets the column type, and returns it as a null-terminated string, with a descriptive prefix.
    
    Note also that this code is intended to be as efficient as possible.
    
    Args: pointer to a buffer to contain the column type, as a null-terminated string
          
    No return code.
*/
void GetGCStatusLoop::GetColumnType(char *type)
{
    char response[50];
    SetGCDeviceReport("GCTY", response);

    // We expect a response like this: "DCTY0000" for None, "DCTY0001" for Conventional, 
    // "DCTY0002" for Directly heated
    int index = 0;
    // Check for "EPKT" first
    if(response[0] == 'E') {
        type[index++] = '*';
        type[index++] = '*';
        type[index++] = ' ';
        type[index++] = 'E';
        type[index++] = 'r';
        type[index++] = 'r';
        type[index++] = 'o';
        type[index++] = 'r';
        type[index++] = ' ';
        type[index++] = '*';
        type[index++] = '*';
    } else {
        switch(response[7]) {
            case '1':
                type[index++] = 'C';
                type[index++] = 'o';
                type[index++] = 'n';
                type[index++] = 'v';
                type[index++] = 'e';
                type[index++] = 'n';
                type[index++] = 't';
                type[index++] = 'i';
                type[index++] = 'o';
                type[index++] = 'n';
                type[index++] = 'a';
                type[index++] = 'l';
                break;
            case '2':
                type[index++] = 'D';
                type[index++] = 'i';
                type[index++] = 'r';
                type[index++] = 'e';
                type[index++] = 'c';
                type[index++] = 't';
                type[index++] = 'l';
                type[index++] = 'y';
                type[index++] = ' ';
                type[index++] = 'h';
                type[index++] = 'e';
                type[index++] = 'a';
                type[index++] = 't';
                type[index++] = 'e';
                type[index++] = 'd';
                break;
            default:                
                type[index++] = 'N';
                type[index++] = 'o';
                type[index++] = 'n';
                type[index++] = 'e';
                break;
        }
    }
    
    type[index++] = '\0';    
}

/*
    Gets the column type, and returns it as a value in the 'ColumnType' enumeration
    
    No arguments.
          
    Return value: the column type
*/
ColumnType GetGCStatusLoop::GetColumnType(void)
{
    char response[50];
    SetGCDeviceReport("GCTY", response);

    // We expect a response like this: "DCTY0000" for None, "DCTY0001" for Conventional, 
    // "DCTY0002" for Directly heated
    // Check for "EPKT" first
    if(response[0] == 'E') {
        return NO_COLUMN;
    }
    
    // 'else'...
    switch(response[7]) {
        case '1':
            return CONVENTIONAL_COLUMN;
        case '2':
            return DIRECTLY_HEATED_COLUMN;
        default:                
            return NO_COLUMN;
    }
}


/*
    Displays the data on the column page, by copying it to the relevant easyGUI variables,
    and calling the relevant functions to actually display it.
    
    Args: a boolean specifying whether or not the display is actually to be updated.
          Even if it is false when we are called, we may set it true if we discover
          one or more data items has changed - but note that we will not set it false 
          if it is already true. If it is true after we have looked at all 
          the home page data, we will then update the display. (If we are called
          with this value set to false, the caller is effectively saying
          'display the column page data only if it has changed').
          
          The column type.
          
          The page number (this varies according to the column type - if the caller knows the column type,
          it must also (in effect) know the page number, so it seems ridiculous for this function 
          to work it out again from the column type)
          
    No return code.
*/
void GetGCStatusLoop::DisplayColumnPageData(bool mustUpdateDisplay, ColumnType columnType, int pageNumber)
{
//    EasyGUIDebugPrint("Column Page", 100, 100);
    // Column temperature and maximum temperature
    char buff[40];
    
#ifdef WANT_DOOR_ACTUATOR_BUTTONS_ON_COLUMN_PAGES
    if(DoorActuatorButtonsHaveChanged(usbDevice, usbHostGC)) {
        mustUpdateDisplay = true;
    }

    SetupDoorActuatorCommandUserInterface(usbDevice, usbHostGC, true);
#endif // WANT_DOOR_ACTUATOR_BUTTONS_ON_COLUMN_PAGES

    GetColumnTemperature(buff, false, true);
    if(strcmp(buff, GuiVar_columnTemperature) != 0) {
        strcpy(GuiVar_columnTemperature, buff);

        //mustUpdateDisplay = true;
        // No - just do this (don't force entire rectangle to redisplay)
        // Hard coded values taken from easyGUI
        RedrawSingleEasyGUIVariableOnComponentPage(400, 110, &GuiVar_columnTemperature, GuiLib_ALIGN_LEFT, COLUMN);
    }
    
    GetColumnTargetTemperature(buff, stringFormatdegCUnits);
    if(strcmp(buff, GuiVar_columnTargetTemperature2) != 0) {
        strcpy(GuiVar_columnTargetTemperature2, buff);

        //mustUpdateDisplay = true;
        // No - just do this (don't force entire rectangle to redisplay)
        // Hard coded values taken from easyGUI
        RedrawSingleEasyGUIVariableOnComponentPage(400, 160, &GuiVar_columnTargetTemperature2, GuiLib_ALIGN_LEFT, COLUMN);
    }
    
    GetColumnMaxTemperature(buff, false, false, true);
    if(strcmp(buff, GuiVar_columnMaxTemp2) != 0) {
        strcpy(GuiVar_columnMaxTemp2, buff);

        //mustUpdateDisplay = true;
        // No - just do this (don't force entire rectangle to redisplay)
        // Hard coded values taken from easyGUI
        RedrawSingleEasyGUIVariableOnComponentPage(400, 230, &GuiVar_columnMaxTemp2, GuiLib_ALIGN_LEFT, COLUMN);
    }

    GetComponentStatusString(COLUMN, buff);
    if(strcmp(buff, GuiVar_columnStatus) != 0) {
        strcpy(GuiVar_columnStatus, buff);

        //mustUpdateDisplay = true;
        // No - just do this (don't force entire rectangle to redisplay)
        // Hard coded values taken from easyGUI
        RedrawSingleEasyGUIVariableOnComponentPage(400, 290, &GuiVar_columnStatus, GuiLib_ALIGN_LEFT, COLUMN);
    }

    if(SinglePageGCComponentStatusHasChanged(COLUMN, lastColumnStatusDisplayedOnColumnPage)) {
        mustUpdateDisplay = true;
    }

    if(mustUpdateDisplay) {
        
#ifdef WANT_COLUMN_STATUS_RECTANGLE
        // Reduce display flickering - get the component status from the GC
        // *before* we call GuiLib_Clear()
        if(singleGCComponentPageStatusColorAreas != NULL) {
            UpdateSingleGCComponentPageStatusColorArea(COLUMN);
        }
#endif
        
#ifndef WANT_COLUMN_STATUS_RECTANGLE
#define WANT_GUILIB_CLEAR // Want this, if no status rectangle
#endif

#ifdef WANT_GUILIB_CLEAR
#ifdef USING_BACKGROUND_BITMAP
        DrawBackgroundBitmap(); // 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
#else
        GuiLib_Clear();
#endif
#undef WANT_GUILIB_CLEAR
#endif
        //...except that redrawing the status rectangle effectively clears the text on top of the rectangle -
        // so we do not need GuiLib_Clear here, since all the column page data appears on top of the rectangle. 
        // Without it, only the text flickers, not the rectangle

#ifdef WANT_COLUMN_STATUS_RECTANGLE
        // Note - we draw the status rectangle 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
        if(singleGCComponentPageStatusColorAreas != NULL) {
            singleGCComponentPageStatusColorAreas->DisplayGCComponentStatus(COLUMN);
            
            lastColumnStatusDisplayedOnColumnPage = singleGCComponentPageStatusColorAreas->GetGCComponentStatus(COLUMN);
        }
#endif        
        // And (currently) we draw the component bitmap on top of the rectangle
        if(qspiBitmaps != NULL) {
            qspiBitmaps->DisplayColumnComponentBitmap();
        }

        GuiLib_ShowScreen(pageNumber, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);
    
#ifdef WANT_DOOR_ACTUATOR_BUTTONS_ON_COLUMN_PAGES
        SetupDoorActuatorCommandUserInterface(usbDevice, usbHostGC, false);
#endif // WANT_DOOR_ACTUATOR_BUTTONS_ON_COLUMN_PAGES

        GuiLib_Refresh();    

#define DEBUG_HERE
#ifdef DEBUG_HERE
    char dbg[100];
    sprintf(dbg, "After GuiLib_Clear 2");
    EasyGUIDebugPrint(dbg, 0, 20);
#undef DEBUG_HERE
#endif
    }
}

/*
    Gets the column length and returns it as a null-terminated string.
    
    This involves getting the value from the settings stored in QSPI memory.
    
    Args: pointer to a buffer to contain the column length, as a null-terminated string.
          This buffer must be at least 10 chars long.
          
    No return code.
*/
void GetGCStatusLoop::GetColumnLength(char *columnLengthBuffer)
{
    if(SettingsHandler::GetSettingValueFromQSPI("ColumnLength", columnLengthBuffer, 10) == 0) {
        // Failed to read the setting - supply a default
        columnLengthBuffer[0] = '0';
        columnLengthBuffer[1] = '\0';
    }
}

/*
    Gets the column inner diameter and returns it as a null-terminated string.
    
    This involves getting the value from the settings stored in QSPI memory.
    
    Args: pointer to a buffer to contain the column inner diameter, as a null-terminated string.
          This buffer must be at least 10 chars long.
          
    No return code.
*/
void GetGCStatusLoop::GetColumnInnerDiameter(char *columnIDBuffer)
{
    if(SettingsHandler::GetSettingValueFromQSPI("ColumnInnerDiameter", columnIDBuffer, 10) == 0) {
        // Failed to read the setting - supply a default
        columnIDBuffer[0] = '0';
        columnIDBuffer[1] = '\0';
    }
}

/*
    Gets the column inner diameter and returns it as a null-terminated string.
    
    This involves getting the value from the settings stored in QSPI memory.
    
    Args: pointer to a buffer to contain the column outer diameter, as a null-terminated string.
          This buffer must be at least 10 chars long.
          
    No return code.
*/
void GetGCStatusLoop::GetColumnOuterDiameter(char *columnODBuffer)
{
    if(SettingsHandler::GetSettingValueFromQSPI("ColumnOuterDiameter", columnODBuffer, 10) == 0) {
        // Failed to read the setting - supply a default
        columnODBuffer[0] = '0';
        columnODBuffer[1] = '\0';
    }
}

/*
    Gets the number of injections (i.e. runs) since the column was installed
    and returns it as a null-terminated string.
    
    This involves getting both values from the settings stored in QSPI memory.
    
    Args: pointer to a buffer to contain the injection count, as a null-terminated string
          
    No return code.
*/
void GetGCStatusLoop::GetInjectionCountSinceColumnInstalled(char *injCount)
{
    int runCount = SettingsHandler::GetIntegerValueFromQSPISettings("RunCount", 0);
    int columnInstalledRunCount = SettingsHandler::GetIntegerValueFromQSPISettings("RunCountWhenColumnInstalled", 0);

    sprintf(injCount, "%d", (runCount - columnInstalledRunCount));
}

/*
    Displays the data on the column information page, by copying it to the relevant easyGUI variables,
    and calling the relevant functions to actually display it.
    
    Args: a boolean specifying whether or not the display is actually to be updated.
          Even if it is false when we are called, we may set it true if we discover
          one or more data items has changed - but note that we will not set it false 
          if it is already true. If it is true after we have looked at all 
          the home page data, we will then update the display. (If we are called
          with this value set to false, the caller is effectively saying
          'display the column page data only if it has changed').
          
          The column type.
          
          The page number (this varies according to the column type - if the caller knows the column type,
          it must also (in effect) know the page number, so it seems ridiculous for this function 
          to work it out again from the column type)
          
          TODO: We no longer have separate pages for the conventional and directly heated columns -
                *** update this function ***
          
    No return code.
*/
void GetGCStatusLoop::DisplayColumnInformationPageData(bool mustUpdateDisplay, ColumnType columnType, int pageNumber)
{
//    EasyGUIDebugPrint("Column Information Page", 100, 100);
    // Column temperature and maximum temperature
    char buff[40];
    
#ifdef WANT_DOOR_ACTUATOR_BUTTONS_ON_COLUMN_PAGES
    if(DoorActuatorButtonsHaveChanged(usbDevice, usbHostGC)) {
        mustUpdateDisplay = true;
    }

    SetupDoorActuatorCommandUserInterface(usbDevice, usbHostGC, true);
#endif // WANT_DOOR_ACTUATOR_BUTTONS_ON_COLUMN_PAGES

    GetColumnType(buff);
    if(strcmp(buff, GuiVar_columnType) != 0) {
        mustUpdateDisplay = true;
        
        strcpy(GuiVar_columnType, buff);
    }
    
    GetColumnMaxTemperature(buff, false, false, true);
    if(strcmp(buff, GuiVar_columnMaxTemp2) != 0) {
        strcpy(GuiVar_columnMaxTemp2, buff);

        //mustUpdateDisplay = true;
        // No - just do this (don't force entire page to redisplay)
        // Hard coded values taken from easyGUI
        RedrawSingleEasyGUIVariableOnComponentPage(400, 150, &GuiVar_columnMaxTemp2, GuiLib_ALIGN_LEFT, COLUMN);
    }

    
    GetColumnLength(buff);
    if(strcmp(buff, GuiVar_columnLength) != 0) {
        mustUpdateDisplay = true;
        
        strcpy(GuiVar_columnLength, buff);
    }
    
    GetColumnInnerDiameter(buff);
    if(strcmp(buff, GuiVar_columnInnerDiameter) != 0) {
        mustUpdateDisplay = true;
        
        strcpy(GuiVar_columnInnerDiameter, buff);
    }
    
    GetColumnOuterDiameter(buff);
    if(strcmp(buff, GuiVar_columnOuterDiameter) != 0) {
        mustUpdateDisplay = true;
        
        strcpy(GuiVar_columnOuterDiameter, buff);
    }
    
    if(SinglePageGCComponentStatusHasChanged(COLUMN)) {
        mustUpdateDisplay = true;
    }

    if(mustUpdateDisplay) {

#ifdef WANT_COLUMN_STATUS_RECTANGLE        
        // Reduce display flickering - get the component status from the GC
        // *before* we call GuiLib_Clear()
        if(singleGCComponentPageStatusColorAreas != NULL) {
            UpdateSingleGCComponentPageStatusColorArea(COLUMN);
        }
#endif       

#ifndef WANT_COLUMN_STATUS_RECTANGLE
#define WANT_GUILIB_CLEAR
#endif

#ifdef WANT_GUILIB_CLEAR
#ifdef USING_BACKGROUND_BITMAP
        DrawBackgroundBitmap(); // 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
#else
        GuiLib_Clear();
#endif
#undef WANT_GUILIB_CLEAR
#endif
        //...except that redrawing the status rectangle effectively clears the text on top of the rectangle -
        // so we do not need GuiLib_Clear here, since all the column page data appears on top of the rectangle. 
        // Without it, only the text flickers, not the rectangle

#ifdef WANT_COLUMN_STATUS_RECTANGLE
        // Note - we draw the status rectangle 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
        if(singleGCComponentPageStatusColorAreas != NULL) {
            singleGCComponentPageStatusColorAreas->DisplayGCComponentStatus(COLUMN);
        }
#endif

        // And (currently) we draw the component bitmap on top of the rectangle
        if(qspiBitmaps != NULL) {
            qspiBitmaps->DisplayColumnComponentBitmap();
        }

        GuiLib_ShowScreen(pageNumber, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);
    
#ifdef WANT_DOOR_ACTUATOR_BUTTONS_ON_COLUMN_PAGES
        SetupDoorActuatorCommandUserInterface(usbDevice, usbHostGC, false);
#endif // WANT_DOOR_ACTUATOR_BUTTONS_ON_COLUMN_PAGES

        GuiLib_Refresh();    

#define DEBUG_HERE
#ifdef DEBUG_HERE
    char dbg[100];
    sprintf(dbg, "After GuiLib_Clear 9");
    EasyGUIDebugPrint(dbg, 0, 20);
#undef DEBUG_HERE
#endif
    }
}


/*
    Gets the initial hold time, and returns it as a null-terminated string.
    
    This code is intended to be as efficient as possible.
    
    Args: pointer to a buffer to contain the run time, as a null-terminated string
          
    No return code.
*/
void GetGCStatusLoop::GetInitialHoldTime(char *time)
{
    char response[50];
    SetGCDeviceReport("GTIM", response);

    // We expect a response like this: "DTIM1234", with run time in units of 0.1 min
    int index = 0;
    
    bool wantNextChars = false;
    if(response[4] != '0') {
        time[index++] = response[4];
        wantNextChars = true;
    }
    if(wantNextChars || (response[5] != '0')) {
        time[index++] = response[5];
    }
    // If the value is zero, make sure we return "0.0" - 
    // we just don't want any zeroes before that
    time[index++] = response[6];
    time[index++] = '.';
    time[index++] = response[7];
}

/*
    Shows or hides, as required, the scroll up and down buttons
    on whichever "XXX Method" page is being displayed.
    Currently, we display the scroll buttons at the same coordinates 
    on all three method pages, i.e. column, injector and gas.
    If these buttons are ever moved from these positions,
    this function will need to be changed.
    *************************************
    
    Args: a boolean saying whether or not to display the scroll buttons
    
    No return code.
    
    It is up to the caller (1) to decide whether the ramps need to be scrolled or not,
    and (2) to verify that an XXX Method page actually *is* the page currently being displayed
*/
void GetGCStatusLoop::ShowMethodPageScrollButtons(bool needToScrollRampData)
{
#define USE_QSPI_ARROW_BITMAPS    
    if(needToScrollRampData){
        // Hard coded coords obtained - these will need to be changed
        // if we change the layout of the Column Method page in easyGUI
#ifdef USE_QSPI_ARROW_BITMAPS
        if(qspiBitmaps != NULL) {
            qspiBitmaps->DisplayUpArrowBitmap(620, 250);
            qspiBitmaps->DisplayDownArrowBitmap(620, 350);
        }
#else
        GuiLib_ShowBitmap(GuiStruct_Bitmap_UpArrow, 630, 260, -1); // No transparency
        GuiLib_ShowBitmap(GuiStruct_Bitmap_DownArrow, 630, 345, -1);
#endif
    } else {
        // Blank out the whole area where the scroll buttons appear.
        // Note that, as always, the first coordinate pair in the call to 'GuiLib_ShowBitmapArea' has to be (0, 0)
        // to display the correct part of the bitmap - not, as one might expect, the same as the second coordinate pair
#ifdef USE_QSPI_ARROW_BITMAPS
        GuiLib_ShowBitmapArea(GuiStruct_Bitmap_BlankBackground, 0, 0, 620, 250, 670, 390, -1); // -1 means 'no transparent colour'
#undef USE_QSPI_ARROW_BITMAPS
#else
        GuiLib_ShowBitmapArea(GuiStruct_Bitmap_BlankBackground, 0, 0, 630, 250, 670, 390, -1); // -1 means 'no transparent colour'
#endif
    }
}

/*
    General function to show or hide the scroll buttons on one of the XXX Method pages.
    
    Args: pointer to the "MethodRampData" instance that is controlling this.
*/
void GetGCStatusLoop::ShowMethodPageScrollButtonsIfNecessary(MethodRampData *methodRampData)
{
    bool needToScrollRampData = false;
    
    if(methodRampData != NULL) {
        needToScrollRampData = (methodRampData->GetScrollRange() > 0);
    }

    ShowMethodPageScrollButtons(needToScrollRampData);
}

/*
    Public function, allowing an external caller to tell us 
    to display, or not, the ramp scroll buttons on the Column Method page.
    
    Assume that the caller knows the Column Method page *is*
    currently being displayed.
*/
void GetGCStatusLoop::ShowColumnMethodPageScrollButtonsIfNecessary(void)
{
    ShowMethodPageScrollButtonsIfNecessary(columnMethodRampData);
}

void GetGCStatusLoop::ScrollColumnMethodRampsUpIfPossible(void)
{
    if(currentPage == GuiStruct_ColumnMethodPage_Def) {
        if(columnMethodPageScrollIndex > 0) {
            --columnMethodPageScrollIndex;
                
            DisplayColumnMethodPageData(false);
        }
    }
}

void GetGCStatusLoop::ScrollColumnMethodRampsDownIfPossible(void)
{
    if(currentPage == GuiStruct_ColumnMethodPage_Def) {
        if(columnMethodRampData != NULL) {
            if(columnMethodPageScrollIndex < columnMethodRampData->GetScrollRange()) {
                ++columnMethodPageScrollIndex;
                
                DisplayColumnMethodPageData(false);
            }
        }
    }
}

/*
    Displays the data on the column method page, by copying it to the relevant easyGUI variables,
    and calling the relevant functions to actually display it.
    
    Args: a boolean specifying whether or not the display is actually to be updated.
          Even if it is false when we are called, we may set it true if we discover
          one or more data items has changed - but note that we will not set it false 
          if it is already true. If it is true after we have looked at all 
          the home page data, we will then update the display. (If we are called
          with this value set to false, the caller is effectively saying
          'display the column page data only if it has changed').
          
    No return code.
*/
void GetGCStatusLoop::DisplayColumnMethodPageData(bool mustUpdateDisplay)
{
    //    EasyGUIDebugPrint("Column Method Page", 100, 100);
    // Column temperature and maximum temperature
    char buff[40];
    
#ifdef WANT_DOOR_ACTUATOR_BUTTONS_ON_COLUMN_PAGES
    if(DoorActuatorButtonsHaveChanged(usbDevice, usbHostGC)) {
        mustUpdateDisplay = true;
    }

    SetupDoorActuatorCommandUserInterface(usbDevice, usbHostGC, true);
#endif // WANT_DOOR_ACTUATOR_BUTTONS_ON_COLUMN_PAGES

    GetColumnTargetTemperature(buff, "%s");
    if(strcmp(buff, GuiVar_columnMethodInitialTemp) != 0) {
        strcpy(GuiVar_columnMethodInitialTemp, buff);

        //mustUpdateDisplay = true;
        // No - just do this (don't force entire page to redisplay)
        // Hard coded values taken from easyGUI
        RedrawSingleEasyGUIVariableOnComponentPage(520, 85, &GuiVar_columnMethodInitialTemp, GuiLib_ALIGN_RIGHT, COLUMN);
    }

    GetInitialHoldTime(buff);
    if(strcmp(buff, GuiVar_columnMethodInitialHold) != 0) {
        strcpy(GuiVar_columnMethodInitialHold, buff);

        //mustUpdateDisplay = true;
        // No - just do this (don't force entire page to redisplay)
        // Hard coded values taken from easyGUI
        RedrawSingleEasyGUIVariableOnComponentPage(520, 120, &GuiVar_columnMethodInitialHold, GuiLib_ALIGN_RIGHT, COLUMN);
    }

    if(columnMethodRampData == NULL) {
        columnMethodRampData = new ColumnMethodRampData(usbDevice, usbHostGC);
    }
    
    if(columnMethodRampData != NULL) {
        if(!columnMethodRampData->GotRampData()) {
            columnMethodRampData->GetRampDataFromGC();
        }
        
        sprintf(buff, "%u", columnMethodRampData->GetRampCount());
        if(strcmp(buff, GuiVar_columnMethodRampCount) != 0) {
            strcpy(GuiVar_columnMethodRampCount, buff);
    
            //mustUpdateDisplay = true;
            // No - just do this (don't force entire page to redisplay)
            // Hard coded values taken from easyGUI
            RedrawSingleEasyGUIVariableOnComponentPage(520, 155, &GuiVar_columnMethodRampCount, GuiLib_ALIGN_RIGHT, COLUMN);
        }
        
        if(columnMethodRampData->NeedToUpdateEasyGUIMethodPageRampVariables()) {
            
            columnMethodPageScrollIndex = 0;
            
            columnMethodRampData->UpdateEasyGUIMethodPageVariables(columnMethodPageScrollIndex);
            
            previousColumnMethodPageScrollIndex = columnMethodPageScrollIndex;
            
            mustUpdateDisplay = true; // Not practical to write all these variables individually

        } else if (previousColumnMethodPageScrollIndex != columnMethodPageScrollIndex) {

            columnMethodRampData->UpdateEasyGUIMethodPageVariables(columnMethodPageScrollIndex);
            
            previousColumnMethodPageScrollIndex = columnMethodPageScrollIndex;
            
            mustUpdateDisplay = true; // Not practical to write all these variables individually
        }
    }
    
    if(SinglePageGCComponentStatusHasChanged(COLUMN)) {
        mustUpdateDisplay = true;
    }

    if(mustUpdateDisplay) {

#ifdef WANT_COLUMN_STATUS_RECTANGLE        
        // Reduce display flickering - get the component status from the GC
        // *before* we call GuiLib_Clear()
        if(singleGCComponentPageStatusColorAreas != NULL) {
            UpdateSingleGCComponentPageStatusColorArea(COLUMN);
        }
#endif       

#ifndef WANT_COLUMN_STATUS_RECTANGLE
#define WANT_GUILIB_CLEAR
#endif

#ifdef WANT_GUILIB_CLEAR
#ifdef USING_BACKGROUND_BITMAP
        DrawBackgroundBitmap(); // 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
#else
        GuiLib_Clear();
#endif
#undef WANT_GUILIB_CLEAR
#endif
        //...except that redrawing the status rectangle effectively clears the text on top of the rectangle -
        // so we do not need GuiLib_Clear here, since all the column page data appears on top of the rectangle. 
        // Without it, only the text flickers, not the rectangle

#ifdef WANT_COLUMN_STATUS_RECTANGLE
        // Note - we draw the status rectangle 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
        if(singleGCComponentPageStatusColorAreas != NULL) {
            singleGCComponentPageStatusColorAreas->DisplayGCComponentStatus(COLUMN);
        }
#endif

        // And (currently) we draw the component bitmap on top of the rectangle
        if(qspiBitmaps != NULL) {
            qspiBitmaps->DisplayColumnComponentBitmap();
        }

        GuiLib_ShowScreen(GuiStruct_ColumnMethodPage_Def, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);
    
#ifdef WANT_DOOR_ACTUATOR_BUTTONS_ON_COLUMN_PAGES
        SetupDoorActuatorCommandUserInterface(usbDevice, usbHostGC, false);
#endif // WANT_DOOR_ACTUATOR_BUTTONS_ON_COLUMN_PAGES

        ShowMethodPageScrollButtonsIfNecessary(columnMethodRampData);

        GuiLib_Refresh();    
    }
}



/*
    Column temperature profile X axis units may be minutes or seconds.
    Sets up the label easyGUI variable appropriately.
*/
void GetGCStatusLoop::SetupColumnTempProfilePageXAxisLabel(TimeUnit timeUnit)
{
    if(timeUnit == SECONDS) {
        strcpy(GuiVar_columnTempProfilePageXAxisLabel, "Time (seconds)");
    } else {
        strcpy(GuiVar_columnTempProfilePageXAxisLabel, "Time (minutes)");
    }
}

/*
    Injector temperature profile X axis units may be minutes or seconds.
    Sets up the label easyGUI variable appropriately.
*/
void GetGCStatusLoop::SetupInjectorTempProfilePageXAxisLabel(TimeUnit timeUnit)
{
    if(timeUnit == SECONDS) {
        strcpy(GuiVar_injectorTempProfilePageXAxisLabel, "Time (seconds)");
    } else {
        strcpy(GuiVar_injectorTempProfilePageXAxisLabel, "Time (minutes)");
    }
}


/*
    Sets up the data for the graph on either the Column Temperature Profile page,
    or the Column (directly heated) Temperature Profile page,
    and causes it to be displayed
*/
void GetGCStatusLoop::DisplayColumnTempProfilePageGraph(ColumnType columnType)
{
    GuiLibGraph* graphToUse = columnTempProfilePageGraph;
    
    if(graphToUse == NULL) {
        return; // Nothing to do
    }
    
    // - dataset 0 is a line representing the temperature profile of the run from 'now' to the finish
    // - dataset 1 is a bar chart representing the temperature profile of the run from 'now' to the finish
    // - dataset 2 is a line representing the temperature profile from the start to 'now'
    // - dataset 3 is a bar chart representing the temperature profile of the run from the start to 'now'
    // - dataset 4 is a single dot at the current time and temperature
    
    TimeUnit timeUnit = MINUTES;
    if(columnTempProfilePageGraphCompleteProfileDataSet->GetTotalMethodTime() < methodTimeUnitsThreshold) {
        timeUnit = SECONDS;
    }
    
    const float yAxisScaleFactor = 10.0f;
    
    // Dataset 0
    graphToUse->SetDataForGraphDataSetInTenthsOfMinutes(0, yAxisScaleFactor, columnTempProfilePageGraphDataSet0);
    
    // Dataset 1 
    graphToUse->SetDataForGraphDataSetInTenthsOfMinutes(1, yAxisScaleFactor, columnTempProfilePageGraphDataSet1);
    
    // Dataset 2
    graphToUse->SetDataForGraphDataSetInTenthsOfMinutes(2, yAxisScaleFactor, columnTempProfilePageGraphDataSet2);
    
    // Dataset 3
    graphToUse->SetDataForGraphDataSetInTenthsOfMinutes(3, yAxisScaleFactor, columnTempProfilePageGraphDataSet3);
    
    // Dataset 4
    graphToUse->SetDataForGraphDataSetInTenthsOfMinutes(4, yAxisScaleFactor, columnTempProfilePageGraphDataSet4);
    
    graphToUse->SetXAxisUnits(timeUnit);

    // The tick sizes must match those set in easyGUI - we do not seem 
    // to be able to get them at runtime
    GuiConst_INT32S minX, maxX;
    //GuiConst_INT32S tickSize = (timeUnit == SECONDS) ? 300 : 100;
    // Always in 1/10 minutes
    GuiConst_INT32S xAxisTickSize = (timeUnit == SECONDS) ? 5 : 100; // 5 == 0.5 minutes (i.e. 30 seconds), 100 == 10 minutes
    columnTempProfilePageGraphCompleteProfileDataSet->GetXAxisRangeInTenthsOfMinutes(&minX, &maxX, xAxisTickSize );
    graphToUse->SetXAxisRange(0, maxX); // Always start X axis at zero
    
    GuiConst_INT32S minY, maxY;
    GuiConst_INT32S yAxisTickSize = 50;
    columnTempProfilePageGraphCompleteProfileDataSet->GetYAxisRange(&minY, &maxY, yAxisTickSize);
    graphToUse->SetYAxisRange(0, (maxY * yAxisScaleFactor)); // Always start Y axis at zero
    
    
    graphToUse->DrawAxes();
    
    // Graph coordinates copied from easyGUI - I cannot find a way of obtaining them at runtime.
    // We need to draw the X axis labels ourselves, since our time values are in units of 0.1 minute,
    // but easyGUI graphs work only in integers - we therefore have to multiply the time values by 10
    // before passing them to easyGUI, and if we let easyGUI draw its own values on the X axis, 
    // they would be 10 times the correct values.
    if(timeUnit == SECONDS) {
        graphToUse->DrawXAxisLabels(0, (maxX * 6), (xAxisTickSize * 6), 130, 340, 500);
    } else {
        graphToUse->DrawXAxisLabels(0, (maxX / 10), (xAxisTickSize / 10), 130, 340, 500);
    }
    // Note that we repeat this call by calling 'DrawXAxisLabels' without arguments 
    // every time we (re)display this page

    // Similar to the X axis - the values we pass to the easyGUI graph are scaled to be larger
    // than the real values, to get round the fact that easyGUI graphs work in integers.
    // (This can cause the profile to appear to be 'stepped', which obviously we do not want.)
    // We must therefore draw the Y axis values ourselves.
    graphToUse->DrawYAxisLabels(0, maxY, yAxisTickSize, 130, 340, 250);
    // Note that we repeat this call by calling 'DrawYAxisLabels' without arguments 
    // every time we (re)display this page

    graphToUse->DrawDataSet(0);
    graphToUse->ShowDataSet(0);

    graphToUse->DrawDataSet(1);
    graphToUse->ShowDataSet(1);

    graphToUse->DrawDataSet(2);
    graphToUse->ShowDataSet(2);

    graphToUse->DrawDataSet(3);
    graphToUse->ShowDataSet(3);

    graphToUse->DrawDataSet(4);
    graphToUse->ShowDataSet(4);

    graphToUse->Redraw();
    
#ifdef TEST_GUILIB_VLINE_PROFILES
    // *** TESTING *** Draw the profile as a solid colour, direct to the display
    //                 Coords manually copied from easyGUI application.
    //                 Boundary between colours is arbitrary for now
    GuiConst_INTCOLOR graphColour1 = SixteenBitColorValue(100, 100, 100);
    GuiConst_INTCOLOR graphColour2 = SixteenBitColorValue(200, 200, 200);
//    double colourBoundaryX = (double) columnTempProfilePageGraphCompleteProfileDataSet->GetTotalMethodTime() / 5.0; // Should be one-fifth across the profile
    double colourBoundaryX = -1.0; // No - use one colour only
    if(timeUnit == SECONDS) {
        columnTempProfilePageGraphCompleteProfileDataSet->DrawUsingGuiLibVLine(140, 330, ((double) 500 / (double) (maxX * 6)), ((double) -250 / (double) maxY), graphColour1, graphColour2, colourBoundaryX);
    } else {
        columnTempProfilePageGraphCompleteProfileDataSet->DrawUsingGuiLibVLine(140, 330, ((double) 500 / (double) (maxX / 10)), ((double) -250 / (double) maxY), graphColour1, graphColour2, colourBoundaryX);
    }
#endif // TEST_GUILIB_VLINE_PROFILES
}

/*
    Displays the data on the column temperature profile page, 
    by copying it to the relevant easyGUI variables,
    and calling the relevant functions to actually display it.
    
    Args: a boolean specifying whether or not the display is actually to be updated.
          Even if it is false when we are called, we may set it true if we discover
          one or more data items has changed - but note that we will not set it false 
          if it is already true. If it is true after we have looked at all 
          the home page data, we will then update the display. (If we are called
          with this value set to false, the caller is effectively saying
          'display the column temperature page data only if it has changed').
          
    No return code.
*/
void GetGCStatusLoop::DisplayColumnTempProfilePageData(bool mustUpdateDisplay, ColumnType columnType, int pageNumber)
{
    // Ensure this is always up to date
    if(needToUpdateProfileGraphs) {
        SetupColumnAndInjectorAndGasProfileData();
        mustUpdateDisplay = true;

        needToUpdateProfileGraphs = false;
    }

    DisplayColumnTempProfilePageGraph(columnType);
    
#ifdef WANT_DOOR_ACTUATOR_BUTTONS_ON_COLUMN_PAGES
    if(DoorActuatorButtonsHaveChanged(usbDevice, usbHostGC)) {
        mustUpdateDisplay = true;
    }

    SetupDoorActuatorCommandUserInterface(usbDevice, usbHostGC, true);
#endif // WANT_DOOR_ACTUATOR_BUTTONS_ON_COLUMN_PAGES

    if(SinglePageGCComponentStatusHasChanged(COLUMN)) {
        mustUpdateDisplay = true;
    }

    if(mustUpdateDisplay) {

#ifdef WANT_COLUMN_STATUS_RECTANGLE
        // Reduce display flickering - get the component status from the GC
        // *before* we call GuiLib_Clear()
        if(singleGCComponentPageStatusColorAreas != NULL) {
            UpdateSingleGCComponentPageStatusColorArea(COLUMN);
        }
#endif

#ifndef WANT_COLUMN_STATUS_RECTANGLE        
        // Makes the display flicker - but omitting it means old text is not cleared from the display
#define WANT_GUILIB_CLEAR
#endif

#ifdef WANT_GUILIB_CLEAR
#ifdef USING_BACKGROUND_BITMAP
        DrawBackgroundBitmap(); // 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
#else
        GuiLib_Clear();
#endif
#undef WANT_GUILIB_CLEAR
#endif
        //...except that redrawing the status rectangle effectively clears the text on top of the rectangle -
        // so we do not need GuiLib_Clear here, since all the injector page data appears on top of the rectangle. 
        // Without it, only the text flickers, not the rectangle

        // Note - we draw the status rectangle 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
#ifdef WANT_COLUMN_STATUS_RECTANGLE
        if(singleGCComponentPageStatusColorAreas != NULL) {
            singleGCComponentPageStatusColorAreas->DisplayGCComponentStatus(COLUMN);
        }
#endif

#ifdef WANT_COMPONENT_ICON_ON_PROFILE_PAGES
        // And (currently) we draw the component bitmap on top of the rectangle
        if(qspiBitmaps != NULL) {
            qspiBitmaps->DisplayColumnComponentBitmap();
        }
#endif // WANT_COMPONENT_ICON_ON_PROFILE_PAGES

        GuiLibGraph* graphToUse = columnTempProfilePageGraph;

        if(graphToUse != NULL) {
            graphToUse->Redraw();
        }
    
// Also in DisplayEasyGUIStructure, main.cpp
//#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);
    
#ifdef TEST_GUILIB_VLINE_PROFILES
        // Now draw the profile as a solid colour, direct to the display.
        // Use the same parameters as the previous call, in DisplayColumnTempProfilePageGraph
        columnTempProfilePageGraphCompleteProfileDataSet->DrawUsingGuiLibVLine();
#endif // TEST_GUILIB_VLINE_PROFILES
#endif // SWIM_TEST

        GuiLib_ShowScreen(pageNumber, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);
        
        // Repeat the previous call to the version of this function with parameters,
        // made from 'DisplayColumnTempProfilePageGraph()' above (it is more convenient 
        // to calculate the parameter values in 'DisplayColumnTempProfilePageGraph()' than here)
        if(graphToUse != NULL) {
            graphToUse->DrawXAxisLabels();
            graphToUse->DrawYAxisLabels();
        }
            
#ifdef WANT_DOOR_ACTUATOR_BUTTONS_ON_COLUMN_PAGES
        SetupDoorActuatorCommandUserInterface(usbDevice, usbHostGC, false);
#endif // WANT_DOOR_ACTUATOR_BUTTONS_ON_COLUMN_PAGES

        GuiLib_Refresh();    
        
#define DEBUG_HERE
#ifdef DEBUG_HERE
    char dbg[100];
    sprintf(dbg, "After GuiLib_Clear 3");
    EasyGUIDebugPrint(dbg, 0, 20);
#undef DEBUG_HERE
#endif
    }
}

    
/*
    Public function that calls DisplayColumnPageData - 
    provided so that 'TouchCallback' (main.cpp) can cause 
    the column page to be redisplayed in response to the user 
    touching one of the up or down buttons on the relevant easyGUI page
*/
void GetGCStatusLoop::RedisplayColumnPage(void)
{
    if(currentPage == GuiStruct_ColumnPage1_2) {
        DisplayColumnPageData(true, CONVENTIONAL_COLUMN, GuiStruct_ColumnPage1_2);
    }
    // else we are displaying a different page - disastrous to call DisplayColumnPageData()
}


void GetGCStatusLoop::DisplayColumnDHAutoCalibrationPageData(bool mustUpdateDisplay)
{
    ColumnDHAutoCalibrationPageHandler *columnDHAutoCalibrationPageHandler = ColumnDHAutoCalibrationPageHandler::GetInstance(usbDevice, usbHostGC);
    
    if(columnDHAutoCalibrationPageHandler != NULL) {
        columnDHAutoCalibrationPageHandler->UpdateVolatileEasyGUIVariables();
    }             
    
    if(DoorActuatorButtonsHaveChanged(usbDevice, usbHostGC)) {
        mustUpdateDisplay = true;
    }

    SetupDoorActuatorCommandUserInterface(usbDevice, usbHostGC, true);
    
    if(SinglePageGCComponentStatusHasChanged(COLUMN)) {
        mustUpdateDisplay = true;
    }

    if(mustUpdateDisplay) {

        // Reduce display flickering - get the component status from the GC
        // *before* we call GuiLib_Clear()
        if(singleGCComponentPageStatusColorAreas != NULL) {
            UpdateSingleGCComponentPageStatusColorArea(COLUMN);
        }
        
        // Makes the display flicker - but omitting it means old text is not cleared from the display
//#define WANT_GUILIB_CLEAR
#ifdef WANT_GUILIB_CLEAR
#ifdef USING_BACKGROUND_BITMAP
        DrawBackgroundBitmap(); // 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
#else
        GuiLib_Clear();
#endif
#undef WANT_GUILIB_CLEAR
#endif
        //...except that redrawing the status rectangle effectively clears the text on top of the rectangle -
        // so we do not need GuiLib_Clear here, since all the injector page data appears on top of the rectangle. 
        // Without it, only the text flickers, not the rectangle

        // Note - we draw the status rectangle 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
#ifdef WANT_STATUS_RECTANGLE_ON_COLUMN_AUTO_CALIB_PAGE        
        if(singleGCComponentPageStatusColorAreas != NULL) {
            singleGCComponentPageStatusColorAreas->DisplayGCComponentStatus(COLUMN);
        }
#else // Need to clear old text some other way...
#ifdef USING_BACKGROUND_BITMAP
        DrawBackgroundBitmap();
#else
        GuiLib_Clear();
#endif // USING_BACKGROUND_BITMAP
#endif // WANT_STATUS_RECTANGLE_ON_COLUMN_AUTO_CALIB_PAGE
        GuiLib_ShowScreen(GuiStruct_ColumnDHAutoCalibrationPage_Def, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);
        
        SetupDoorActuatorCommandUserInterface(usbDevice, usbHostGC, false);
        
        GuiLib_Refresh();    

#define DEBUG_HERE
#ifdef DEBUG_HERE
    char dbg[100];
    sprintf(dbg, "After GuiLib_Clear 94");
    EasyGUIDebugPrint(dbg, 0, 20);
#undef DEBUG_HERE
#endif
    }
}


/*
    A "simple page" is one that only has fixed text, rectangles, etc, and easyGUI variables.
    No status rectangles, etc.
    
    All these can be displayed using the same sequence of function calls.
*/
void GetGCStatusLoop::DisplaySimplePageData(int pageNumber, bool mustUpdateDisplay)
{
    // There are no status rectangles on a "simple page"

    // Makes the display flicker - but omitting it means old text is not cleared from the display
#define WANT_GUILIB_CLEAR
#ifdef WANT_GUILIB_CLEAR
#ifdef USING_BACKGROUND_BITMAP
    DrawBackgroundBitmap(); 
#else
    GuiLib_Clear();
#endif
#undef WANT_GUILIB_CLEAR
#endif
    GuiLib_ShowScreen(pageNumber, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);
    
    GuiLib_Refresh();    

//#define DEBUG_HERE
#ifdef DEBUG_HERE
    char dbg[100];
    sprintf(dbg, "After GuiLib_Clear 95");
    EasyGUIDebugPrint(dbg, 0, 20);
#undef DEBUG_HERE
#endif
}

void GetGCStatusLoop::DisplayColumnDHManualCalibrationPageData(bool mustUpdateDisplay)
{
    DisplaySimplePageData(GuiStruct_ColumnDHManualCalibrationPage_Def, mustUpdateDisplay);
}

void GetGCStatusLoop::DisplayColumnDHSensorCalibrationPageData(bool mustUpdateDisplay)
{
    DisplaySimplePageData(GuiStruct_ColumnDHSensorCalibration_Def, mustUpdateDisplay);
}

void GetGCStatusLoop::DisplayColumnDHPSUDACPageData(bool mustUpdateDisplay)
{
    if(mustUpdateDisplay) {
        ColumnDHPSUDACPageHandler *columnDHPSUDACPageHandler = ColumnDHPSUDACPageHandler::GetInstance(usbDevice, usbHostGC);
        
        if(columnDHPSUDACPageHandler != NULL) {
            columnDHPSUDACPageHandler->DisplayingEasyGUIPage(true);
        }
    }
        
    DisplaySimplePageData(GuiStruct_PSU_DAC_Page_Def, mustUpdateDisplay);
}

void GetGCStatusLoop::DisplayColumnOvenNudgeAndDampPageData(bool mustUpdateDisplay)
{
    DisplaySimplePageData(GuiStruct_ColumnOvenNudgeAndDampPage_0, mustUpdateDisplay);
}

void GetGCStatusLoop::DisplayColumnDHNudgeAndDampPageData(bool mustUpdateDisplay)
{
    DisplaySimplePageData(GuiStruct_ColumnDHNudgeAndDampPage_0, mustUpdateDisplay);
}

void GetGCStatusLoop::DisplayInjectorNudgeAndDampPageData(bool mustUpdateDisplay)
{
    DisplaySimplePageData(GuiStruct_InjectorNudgeAndDampPage_0, mustUpdateDisplay);
}

void GetGCStatusLoop::DisplayDetectorNudgeAndDampPageData(bool mustUpdateDisplay)
{
    DisplaySimplePageData(GuiStruct_DetectorNudgeAndDampPage_0, mustUpdateDisplay);
}

void GetGCStatusLoop::DisplayAuxiliaryNudgeAndDampPageData(bool mustUpdateDisplay)
{
    DisplaySimplePageData(GuiStruct_AuxiliaryNudgeAndDampPage_0, mustUpdateDisplay);
}

void GetGCStatusLoop::DisplayFanPowerPageData(bool mustUpdateDisplay)
{
    DisplaySimplePageData(GuiStruct_FanPowerPage_0, mustUpdateDisplay);
}

void GetGCStatusLoop::DisplayDebugCommandsPageData(bool mustUpdateDisplay)
{
    DisplaySimplePageData(GuiStruct_DebugCommandsPage_Def, mustUpdateDisplay);
}


/*
    Gets the injection mode, and returns it as a null-terminated string, with a descriptive prefix.
    
    Note also that this code is intended to be as efficient as possible.
    
    Args: pointer to a buffer to contain the injection mode, as a null-terminated string
    
    Does not put a hard-coded prefix on the string it returns.
          
    No return code.
*/
void GetGCStatusLoop::GetInjectionMode(char *mode)
{
    char response[50];
    SetGCDeviceReport("GIMD", response);
    // We expect a response like this: "DIMD0000" for Split, "DIMD0001" for Splitless,
    // "DIMD0002" for On Column, "DIMD0003" for Gas Sampling
    
    int index = 0;
    // Check for "EPKT" first
    if(response[0] == 'E') {
        mode[index++] = '*';
        mode[index++] = '*';
        mode[index++] = ' ';
        mode[index++] = 'E';
        mode[index++] = 'r';
        mode[index++] = 'r';
        mode[index++] = 'o';
        mode[index++] = 'r';
        mode[index++] = ' ';
        mode[index++] = '*';
        mode[index++] = '*';
    } else {
        switch(response[7]) {
            case 1:
                mode[index++] = 'S';
                mode[index++] = 'p';
                mode[index++] = 'l';
                mode[index++] = 'i';
                mode[index++] = 't';
                mode[index++] = 'l';
                mode[index++] = 'e';
                mode[index++] = 's';
                mode[index++] = 's';
                break;
            case 2:
                mode[index++] = 'O';
                mode[index++] = 'n';
                mode[index++] = ' ';
                mode[index++] = 'C';
                mode[index++] = 'o';
                mode[index++] = 'l';
                mode[index++] = 'u';
                mode[index++] = 'm';
                mode[index++] = 'n';
                break;
            case 3:
                mode[index++] = 'G';
                mode[index++] = 'a';
                mode[index++] = 's';
                mode[index++] = ' ';
                mode[index++] = 's';
                mode[index++] = 'a';
                mode[index++] = 'm';
                mode[index++] = 'p';
                mode[index++] = 'l';
                mode[index++] = 'i';
                mode[index++] = 'n';
                mode[index++] = 'g';
                break;
            default:
                mode[index++] = 'S';
                mode[index++] = 'p';
                mode[index++] = 'l';
                mode[index++] = 'i';
                mode[index++] = 't';
                break;
        }
    }

    mode[index++] = '\0';
}

/*
    Gets the injector type, and returns it as a null-terminated string, with a descriptive prefix.
    
    Note also that this code is intended to be as efficient as possible.
    
    Args: pointer to a buffer to contain the injection mode, as a null-terminated string
          boolean set true if the caller wants a full prefix ("Injector type: "), 
          or false for a short prefix ("Type: ")
          
    No return code.
*/
void GetGCStatusLoop::GetInjectorType(char *mode, bool wantPrefix)
{
    char response[50];
    SetGCDeviceReport("GITY", response);

    // We expect a response like this: "DITY0000" for None, 
    // "DITY0001" for standard (split/splitless), "DITY0002" for PTV
    int index = 0;
    if(wantPrefix) {
        mode[index++]  = 'I';
        mode[index++]  = 'n';
        mode[index++]  = 'j';
        mode[index++]  = 'e';
        mode[index++]  = 'c';
        mode[index++]  = 't';
        mode[index++]  = 'o';
        mode[index++]  = 'r';
        mode[index++]  = ' ';
        mode[index++]  = 't';
        mode[index++]  = 'y';
        mode[index++]  = 'p';
        mode[index++]  = 'e';
        mode[index++]  = ':';
        mode[index++]  = ' ';
    }
        
    // Check for "EPKT" first
    if(response[0] == 'E') {
        mode[index++] = '*';
        mode[index++] = '*';
        mode[index++] = ' ';
        mode[index++] = 'E';
        mode[index++] = 'r';
        mode[index++] = 'r';
        mode[index++] = 'o';
        mode[index++] = 'r';
        mode[index++] = ' ';
        mode[index++] = '*';
        mode[index++] = '*';
        mode[index++] = '\0';
    } else {
        switch(response[7]) {
            case '1':
                mode[index++] = 'S';
                mode[index++] = 't';
                mode[index++] = 'a';
                mode[index++] = 'n';
                mode[index++] = 'd';
                mode[index++] = 'a';
                mode[index++] = 'r';
                mode[index++] = 'd';
                break;
            case '2':
                mode[index++] = 'P';
                mode[index++] = 'T';
                mode[index++] = 'V';
                break;
            default:
                mode[index++] = 'N';
                mode[index++] = 'o';
                mode[index++] = 'n';
                mode[index++] = 'e';
                break;
        }
        mode[index++] = '\0';
    }
}

/*
    Gets the injection split time, and returns it as a null-terminated string.
    
    Note also that this code is intended to be as efficient as possible.
    
    Args: pointer to a buffer to contain the split time, as a null-terminated string
    
    Does not put a hard-coded prefix on the string it returns,
    but does put " min" as a suffix
          
    No return code.
*/
void GetGCStatusLoop::GetInjectorSplitTime(char *splitTime)
{
    char response[50];
    SetGCDeviceReport("GSPT", response);
    // We expect a response like this: "DSPTnnnn", where "nnnn" is the split time,
    // in units of 0.1 min.
    
    int index = 0;
    // Check for "EPKT" first
    if(response[0] == 'E') {
        splitTime[index++] = '*';
        splitTime[index++] = '*';
        splitTime[index++] = ' ';
        splitTime[index++] = 'E';
        splitTime[index++] = 'r';
        splitTime[index++] = 'r';
        splitTime[index++] = 'o';
        splitTime[index++] = 'r';
        splitTime[index++] = ' ';
        splitTime[index++] = '*';
        splitTime[index++] = '*';
    } else {
        bool wantNextChars = false;
        if(response[4] != '0') {
            splitTime[index++] = response[4];
            wantNextChars = true;
        }
        if(wantNextChars || (response[5] != '0')) {
            splitTime[index++] = response[5];
        }
        // If the value is zero, make sure we return "0.0" - 
        // we just don't want any zeroes before that
        splitTime[index++] = response[6];
        splitTime[index++] = '.';
        splitTime[index++] = response[7];
        splitTime[index++] = ' ';
        splitTime[index++] = 'm';
        splitTime[index++] = 'i';
        splitTime[index++] = 'n';
    }
    
    splitTime[index] = '\0';
}


/*
    Displays the data on the injector page, by copying it to the relevant easyGUI variables,
    and calling the relevant functions to actually display it.
    
    Args: a boolean specifying whether or not the display is actually to be updated.
          Even if it is false when we are called, we may set it true if we discover
          one or more data items has changed - but note that we will not set it false 
          if it is already true. If it is true after we have looked at all 
          the home page data, we will then update the display. (If we are called
          with this value set to false, the caller is effectively saying
          'display the injector page data only if it has changed').
          
    No return code.
*/
void GetGCStatusLoop::DisplayInjectorPageData(bool mustUpdateDisplay)
{
//    EasyGUIDebugPrint("Injector Page", 100, 100);
    // Injector temperature and mode
    char buff[40];
    
    GetInjectorTemperature(buff, false);
    if(strcmp(buff, GuiVar_injectorTemperature) != 0) {
        strcpy(GuiVar_injectorTemperature, buff);

        //mustUpdateDisplay = true;
        // No - just do this (don't force entire rectangle to redisplay)
        // Hard coded values taken from easyGUI
        RedrawSingleEasyGUIVariableOnComponentPage(410, 90, &GuiVar_injectorTemperature, GuiLib_ALIGN_LEFT, INJECTOR);
    }

    GetInjectorTargetTemperature(buff, stringFormatdegCUnits);
    if(strcmp(buff, GuiVar_injectorTargetTemperature2) != 0) {
        strcpy(GuiVar_injectorTargetTemperature2, buff);

        //mustUpdateDisplay = true;
        // No - just do this (don't force entire rectangle to redisplay)
        // Hard coded values taken from easyGUI
        RedrawSingleEasyGUIVariableOnComponentPage(410, 130, &GuiVar_injectorTargetTemperature2, GuiLib_ALIGN_LEFT, INJECTOR);
    }

    GetInjectionMode(buff);
    if(strcmp(buff, GuiVar_injectionMode2) != 0) {
        strcpy(GuiVar_injectionMode2, buff);

        //mustUpdateDisplay = true;
        // No - just do this (don't force entire rectangle to redisplay)
        // Hard coded values taken from easyGUI
        RedrawSingleEasyGUIVariableOnComponentPage(410, 170, &GuiVar_injectionMode2, GuiLib_ALIGN_LEFT, INJECTOR);
    }

    GetInjectorType(buff, false);
    if(strcmp(buff, GuiVar_injectorType) != 0) {
        strcpy(GuiVar_injectorType, buff);

        //mustUpdateDisplay = true;
        // No - just do this (don't force entire rectangle to redisplay)
        // Hard coded values taken from easyGUI
        RedrawSingleEasyGUIVariableOnComponentPage(410, 210, &GuiVar_injectorType, GuiLib_ALIGN_LEFT, INJECTOR);
    }

    GetComponentStatusString(INJECTOR, buff);
    if(strcmp(buff, GuiVar_injectorStatus) != 0) {
        strcpy(GuiVar_injectorStatus, buff);

        //mustUpdateDisplay = true;
        // No - just do this (don't force entire rectangle to redisplay)
        // Hard coded values taken from easyGUI
        RedrawSingleEasyGUIVariableOnComponentPage(410, 250, &GuiVar_injectorStatus, GuiLib_ALIGN_LEFT, INJECTOR);
    }
    
    if(SinglePageGCComponentStatusHasChanged(INJECTOR, lastInjectorStatusDisplayedOnInjectorPage)) {
        mustUpdateDisplay = true;
    }

    if(mustUpdateDisplay) {

#ifdef WANT_INJECTOR_STATUS_RECTANGLE
        // Reduce display flickering - get the component status from the GC
        // *before* we call GuiLib_Clear()
        if(singleGCComponentPageStatusColorAreas != NULL) {
            UpdateSingleGCComponentPageStatusColorArea(INJECTOR);
        }
#endif

#ifndef WANT_INJECTOR_STATUS_RECTANGLE
        // Makes the display flicker - but omitting it means old text is not cleared from the display
#define WANT_GUILIB_CLEAR
#endif

#ifdef WANT_GUILIB_CLEAR
#ifdef USING_BACKGROUND_BITMAP
        DrawBackgroundBitmap(); // 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
#else
        GuiLib_Clear();
#endif
#undef WANT_GUILIB_CLEAR
#endif
        //...except that redrawing the status rectangle effectively clears the text on top of the rectangle -
        // so we do not need GuiLib_Clear here, since all the injector page data appears on top of the rectangle. 
        // Without it, only the text flickers, not the rectangle

#ifdef WANT_INJECTOR_STATUS_RECTANGLE
        // Note - we draw the status rectangle 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
        if(singleGCComponentPageStatusColorAreas != NULL) {
            singleGCComponentPageStatusColorAreas->DisplayGCComponentStatus(INJECTOR);
            
            lastInjectorStatusDisplayedOnInjectorPage = singleGCComponentPageStatusColorAreas->GetGCComponentStatus(INJECTOR);
        }
#endif
        
        // And (currently) we draw the component bitmap on top of the rectangle
        if(qspiBitmaps != NULL) {
            qspiBitmaps->DisplayInjectorComponentBitmap();
        }

        GuiLib_ShowScreen(GuiStruct_InjectorPage1_3, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);
    
        GuiLib_Refresh();    

#define DEBUG_HERE
#ifdef DEBUG_HERE
    char dbg[100];
    sprintf(dbg, "After GuiLib_Clear 3");
    EasyGUIDebugPrint(dbg, 0, 20);
#undef DEBUG_HERE
#endif
    }
}

/*
    Public function, allowing an external caller to tell us 
    to display, or not, the ramp scroll buttons on the Injector Method page.
    
    Assume that the caller knows the Injector Method page *is*
    currently being displayed.
*/
void GetGCStatusLoop::ShowInjectorMethodPageScrollButtonsIfNecessary(void)
{
    ShowMethodPageScrollButtonsIfNecessary(injectorMethodRampData);
}

void GetGCStatusLoop::ScrollInjectorMethodRampsUpIfPossible(void)
{
    if(currentPage == GuiStruct_InjectorMethodPage_Def) {
        if(injectorMethodPageScrollIndex > 0) {
            --injectorMethodPageScrollIndex;
                
            DisplayInjectorMethodPageData(false);
        }
    }
}

void GetGCStatusLoop::ScrollInjectorMethodRampsDownIfPossible(void)
{
    if(currentPage == GuiStruct_InjectorMethodPage_Def) {
        if(injectorMethodRampData != NULL) {
            if(injectorMethodPageScrollIndex < injectorMethodRampData->GetScrollRange()) {
                ++injectorMethodPageScrollIndex;
                
                DisplayInjectorMethodPageData(false);
            }
        }
    }
}

/*
    Displays the data on the injector method page, by copying it to the relevant easyGUI variables,
    and calling the relevant functions to actually display it.
    
    Args: a boolean specifying whether or not the display is actually to be updated.
          Even if it is false when we are called, we may set it true if we discover
          one or more data items has changed - but note that we will not set it false 
          if it is already true. If it is true after we have looked at all 
          the home page data, we will then update the display. (If we are called
          with this value set to false, the caller is effectively saying
          'display the column page data only if it has changed').
          
    No return code.
*/
void GetGCStatusLoop::DisplayInjectorMethodPageData(bool mustUpdateDisplay)
{
    char buff[40];
    
    GetInjectorTargetTemperature(buff, "%s");
    if(strcmp(buff, GuiVar_injectorMethodInitialTemp) != 0) {
        strcpy(GuiVar_injectorMethodInitialTemp, buff);

        //mustUpdateDisplay = true;
        // No - just do this (don't force entire page to redisplay)
        // Hard coded values taken from easyGUI
        RedrawSingleEasyGUIVariableOnComponentPage(520, 85, &GuiVar_injectorMethodInitialTemp, GuiLib_ALIGN_RIGHT, COLUMN);
    }

    GetInitialHoldTime(buff);
    if(strcmp(buff, GuiVar_injectorMethodInitialHold) != 0) {
        strcpy(GuiVar_injectorMethodInitialHold, buff);

        //mustUpdateDisplay = true;
        // No - just do this (don't force entire page to redisplay)
        // Hard coded values taken from easyGUI
        RedrawSingleEasyGUIVariableOnComponentPage(520, 120, &GuiVar_injectorMethodInitialHold, GuiLib_ALIGN_RIGHT, COLUMN);
    }

    if(injectorMethodRampData == NULL) {
        injectorMethodRampData = new InjectorMethodRampData(usbDevice, usbHostGC);
    }
    
    if(injectorMethodRampData != NULL) {
        if(!injectorMethodRampData->GotRampData()) {
            injectorMethodRampData->GetRampDataFromGC();
        }
        
        sprintf(buff, "%u", injectorMethodRampData->GetRampCount());
        if(strcmp(buff, GuiVar_injectorMethodRampCount) != 0) {
            strcpy(GuiVar_injectorMethodRampCount, buff);
    
            //mustUpdateDisplay = true;
            // No - just do this (don't force entire page to redisplay)
            // Hard coded values taken from easyGUI
            RedrawSingleEasyGUIVariableOnComponentPage(520, 155, &GuiVar_injectorMethodRampCount, GuiLib_ALIGN_RIGHT, COLUMN);
        }
        
        if(injectorMethodRampData->NeedToUpdateEasyGUIMethodPageRampVariables()) {
            
            injectorMethodPageScrollIndex = 0;
            
            injectorMethodRampData->UpdateEasyGUIMethodPageVariables(injectorMethodPageScrollIndex);
            
            previousInjectorMethodPageScrollIndex = injectorMethodPageScrollIndex;
            
            mustUpdateDisplay = true; // Not practical to write all these variables individually

        } else if (previousInjectorMethodPageScrollIndex != injectorMethodPageScrollIndex) {

            injectorMethodRampData->UpdateEasyGUIMethodPageVariables(injectorMethodPageScrollIndex);
            
            previousInjectorMethodPageScrollIndex = injectorMethodPageScrollIndex;
            
            mustUpdateDisplay = true; // Not practical to write all these variables individually
        }
    }
    
    if(SinglePageGCComponentStatusHasChanged(INJECTOR)) {
        mustUpdateDisplay = true;
    }

    if(mustUpdateDisplay) {

#ifdef WANT_INJECTOR_STATUS_RECTANGLE        
        // Reduce display flickering - get the component status from the GC
        // *before* we call GuiLib_Clear()
        if(singleGCComponentPageStatusColorAreas != NULL) {
            UpdateSingleGCComponentPageStatusColorArea(INJECTOR);
        }
#endif       

#ifndef WANT_INJECTOR_STATUS_RECTANGLE
#define WANT_GUILIB_CLEAR
#endif

#ifdef WANT_GUILIB_CLEAR
#ifdef USING_BACKGROUND_BITMAP
        DrawBackgroundBitmap(); // 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
#else
        GuiLib_Clear();
#endif
#undef WANT_GUILIB_CLEAR
#endif
        //...except that redrawing the status rectangle effectively clears the text on top of the rectangle -
        // so we do not need GuiLib_Clear here, since all the column page data appears on top of the rectangle. 
        // Without it, only the text flickers, not the rectangle

#ifdef WANT_INJECTOR_STATUS_RECTANGLE
        // Note - we draw the status rectangle 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
        if(singleGCComponentPageStatusColorAreas != NULL) {
            singleGCComponentPageStatusColorAreas->DisplayGCComponentStatus(INJECTOR);
        }
#endif

        // And (currently) we draw the component bitmap on top of the rectangle
        if(qspiBitmaps != NULL) {
            qspiBitmaps->DisplayInjectorComponentBitmap();
        }

        GuiLib_ShowScreen(GuiStruct_InjectorMethodPage_Def, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);
    
        ShowMethodPageScrollButtonsIfNecessary(injectorMethodRampData);

        GuiLib_Refresh();    
    }
}


/*
    Sets up the data for the graph on the Injector Temperature Profile page,
    and causes it to be displayed
*/
void GetGCStatusLoop::DisplayInjectorTempProfilePageGraph(void)
{
    // - dataset 0 is a line representing the temperature profile of the run from 'now' to the finish
    // - dataset 1 is a bar chart representing the temperature profile of the run from 'now' to the finish
    // - dataset 2 is a line representing the temperature profile from the start to 'now'
    // - dataset 3 is a bar chart representing the temperature profile of the run from the start to 'now'
    // - dataset 4 is a single dot at the current time and temperature
    
    TimeUnit timeUnit = MINUTES;
    if(injectorTempProfilePageGraphCompleteProfileDataSet->GetTotalMethodTime() < methodTimeUnitsThreshold) {
        timeUnit = SECONDS;
    }

    const float yAxisScaleFactor = 10.0f;

    // Dataset 0
    injectorTempProfilePageGraph->SetDataForGraphDataSetInTenthsOfMinutes(0, yAxisScaleFactor, injectorTempProfilePageGraphDataSet0);
    
    // Dataset 1 
    injectorTempProfilePageGraph->SetDataForGraphDataSetInTenthsOfMinutes(1, yAxisScaleFactor, injectorTempProfilePageGraphDataSet1);
    
    // Dataset 2
    injectorTempProfilePageGraph->SetDataForGraphDataSetInTenthsOfMinutes(2, yAxisScaleFactor, injectorTempProfilePageGraphDataSet2);
    
    // Dataset 3
    injectorTempProfilePageGraph->SetDataForGraphDataSetInTenthsOfMinutes(3, yAxisScaleFactor, injectorTempProfilePageGraphDataSet3);
    
    // Dataset 4
    injectorTempProfilePageGraph->SetDataForGraphDataSetInTenthsOfMinutes(4, yAxisScaleFactor, injectorTempProfilePageGraphDataSet4);
    
    injectorTempProfilePageGraph->SetXAxisUnits(timeUnit);
    
    if(timeUnit == SECONDS) {
        strcpy(GuiVar_injectorTempProfilePageXAxisLabel, "Time (seconds)");
    } else {
        strcpy(GuiVar_injectorTempProfilePageXAxisLabel, "Time (minutes)");
    }

    if(injectorTempProfilePageGraphCompleteProfileDataSet->GetPointCount() == 0) {
        strcpy(GuiVar_injectorTempProfilePageNoMethod, "No method set up");
    } else {
        GuiVar_injectorTempProfilePageNoMethod[0] = '\0';
    }

    // The tick sizes must match those set in easyGUI - we do not seem 
    // to be able to get them at runtime
    GuiConst_INT32S minX, maxX;
    //GuiConst_INT32S tickSize = (timeUnit == SECONDS) ? 300 : 100;
    // Always in 1/10 minutes
    GuiConst_INT32S xAxisTickSize = (timeUnit == SECONDS) ? 5 : 100; // 5 == 0.5 minutes (i.e. 30 seconds), 100 == 10 minutes
    injectorTempProfilePageGraphCompleteProfileDataSet->GetXAxisRangeInTenthsOfMinutes(&minX, &maxX, xAxisTickSize);
    injectorTempProfilePageGraph->SetXAxisRange(0, maxX); // Always start X axis at zero
    
    GuiConst_INT32S minY, maxY;
    GuiConst_INT32S yAxisTickSize = 50;
    injectorTempProfilePageGraphCompleteProfileDataSet->GetYAxisRange(&minY, &maxY, yAxisTickSize);
    injectorTempProfilePageGraph->SetYAxisRange(0, (maxY * yAxisScaleFactor)); // Always start Y axis at zero
    
    
    injectorTempProfilePageGraph->DrawAxes();

    // We need to draw the X axis labels ourselves, since our time values are in units of 0.1 minute,
    // but easyGUI graphs work only in integers - we therefore have to multiply the time values by 10
    // before passing them to easyGUI, and if we let easyGUI draw its own values on the X axis, 
    // they would be 10 times the correct values.
    // Graph coordinates copied from easyGUI - I cannot find a way of obtaining them at runtime.
    if(timeUnit == SECONDS) {
        injectorTempProfilePageGraph->DrawXAxisLabels(0, (maxX * 6), (xAxisTickSize * 6), 130, 340, 500);
    } else {
        injectorTempProfilePageGraph->DrawXAxisLabels(0, (maxX / 10), (xAxisTickSize / 10), 130, 340, 500);
    }
    // Note that we repeat this call by calling 'DrawXAxisLabels' without arguments 
    // every time we (re)display this page

    // Similar to the X axis - the values we pass to the easyGUI graph are scaled to be larger
    // than the real values, to get round the fact that easyGUI graphs work in integers.
    // (This can cause the profile to appear to be 'stepped', which obviously we do not want.)
    // We must therefore draw the Y axis values ourselves.
    injectorTempProfilePageGraph->DrawYAxisLabels(0, maxY, yAxisTickSize, 130, 340, 250);
    // Note that we repeat this call by calling 'DrawYAxisLabels' without arguments 
    // every time we (re)display this page

    injectorTempProfilePageGraph->DrawDataSet(0);
    injectorTempProfilePageGraph->ShowDataSet(0);

    injectorTempProfilePageGraph->DrawDataSet(1);
    injectorTempProfilePageGraph->ShowDataSet(1);

    injectorTempProfilePageGraph->DrawDataSet(2);
    injectorTempProfilePageGraph->ShowDataSet(2);

    injectorTempProfilePageGraph->DrawDataSet(3);
    injectorTempProfilePageGraph->ShowDataSet(3);

    injectorTempProfilePageGraph->DrawDataSet(4);
    injectorTempProfilePageGraph->ShowDataSet(4);

    injectorTempProfilePageGraph->Redraw();

#ifdef TEST_GUILIB_VLINE_PROFILES
    // *** TESTING *** Draw the profile as a solid colour, direct to the display
    //                 Coords manually copied from easyGUI application.
    //                 Boundary between colours is arbitrary for now
    GuiConst_INTCOLOR graphColour1 = SixteenBitColorValue(100, 100, 100);
    GuiConst_INTCOLOR graphColour2 = SixteenBitColorValue(200, 200, 200);
//    double colourBoundaryX = (double) injectorTempProfilePageGraphCompleteProfileDataSet->GetTotalMethodTime() / 5.0; // Should be one-fifth across the profile
    double colourBoundaryX = -1.0; // No - use one colour only
    if(timeUnit == SECONDS) {
        injectorTempProfilePageGraphCompleteProfileDataSet->DrawUsingGuiLibVLine(140, 330, ((double) 500 / (double) (maxX * 6)), ((double) -250 / (double) maxY), graphColour1, graphColour2, colourBoundaryX);
    } else {
        injectorTempProfilePageGraphCompleteProfileDataSet->DrawUsingGuiLibVLine(140, 330, ((double) 500 / (double) (maxX / 10)), ((double) -250 / (double) maxY), graphColour1, graphColour2, colourBoundaryX);
    }
#endif // TEST_GUILIB_VLINE_PROFILES
}

/*
    Displays the data on the injector temperature profile page, 
    by copying it to the relevant easyGUI variables,
    and calling the relevant functions to actually display it.
    
    Args: a boolean specifying whether or not the display is actually to be updated.
          Even if it is false when we are called, we may set it true if we discover
          one or more data items has changed - but note that we will not set it false 
          if it is already true. If it is true after we have looked at all 
          the home page data, we will then update the display. (If we are called
          with this value set to false, the caller is effectively saying
          'display the injector page data only if it has changed').
          
    No return code.
*/
void GetGCStatusLoop::DisplayInjectorTempProfilePageData(bool mustUpdateDisplay)
{
    // Ensure this is always up to date
    if(needToUpdateProfileGraphs) {
        SetupColumnAndInjectorAndGasProfileData();
        mustUpdateDisplay = true;
        
        needToUpdateProfileGraphs = false;
    }

    DisplayInjectorTempProfilePageGraph();
    
    if(SinglePageGCComponentStatusHasChanged(INJECTOR)) {
        mustUpdateDisplay = true;
    }

    if(mustUpdateDisplay) {

#ifdef WANT_INJECTOR_STATUS_RECTANGLE
        // Reduce display flickering - get the component status from the GC
        // *before* we call GuiLib_Clear()
        if(singleGCComponentPageStatusColorAreas != NULL) {
            UpdateSingleGCComponentPageStatusColorArea(INJECTOR);
        }
#endif
        
#ifndef WANT_INJECTOR_STATUS_RECTANGLE
        // Makes the display flicker - but omitting it means old text is not cleared from the display
#define WANT_GUILIB_CLEAR
#endif

#ifdef WANT_GUILIB_CLEAR
#ifdef USING_BACKGROUND_BITMAP
        DrawBackgroundBitmap(); // 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
#else
        GuiLib_Clear();
#endif
#undef WANT_GUILIB_CLEAR
#endif
        //...except that redrawing the status rectangle effectively clears the text on top of the rectangle -
        // so we do not need GuiLib_Clear here, since all the injector page data appears on top of the rectangle. 
        // Without it, only the text flickers, not the rectangle

#ifdef WANT_INJECTOR_STATUS_RECTANGLE
        // Note - we draw the status rectangle 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
        if(singleGCComponentPageStatusColorAreas != NULL) {
            singleGCComponentPageStatusColorAreas->DisplayGCComponentStatus(INJECTOR);
        }
#endif

#ifdef WANT_COMPONENT_ICON_ON_PROFILE_PAGES
        // And (currently) we draw the component bitmap on top of the rectangle
        if(qspiBitmaps != NULL) {
            qspiBitmaps->DisplayInjectorComponentBitmap();
        }
#endif // WANT_COMPONENT_ICON_ON_PROFILE_PAGES

        injectorTempProfilePageGraph->Redraw();
    
#ifdef TEST_GUILIB_VLINE_PROFILES
        // Now draw the profile as a solid colour, direct to the display.
        // Use the same parameters as the previous call, in DisplayInjectorTempProfilePageGraph
        injectorTempProfilePageGraphCompleteProfileDataSet->DrawUsingGuiLibVLine();
#endif // TEST_GUILIB_VLINE_PROFILES

        GuiLib_ShowScreen(GuiStruct_InjectorTempProfilePage_25, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);
    
        // Repeat the previous call to the version of this function with parameters,
        // made from 'DisplayInjectorTempProfilePageGraph()' above (it is more convenient 
        // to calculate the parameter values in 'DisplayInjectorTempProfilePageGraph()' than here)
        injectorTempProfilePageGraph->DrawXAxisLabels();
        injectorTempProfilePageGraph->DrawYAxisLabels();
    
        GuiLib_Refresh();    

#define DEBUG_HERE
#ifdef DEBUG_HERE
    char dbg[100];
    sprintf(dbg, "After GuiLib_Clear 3");
    EasyGUIDebugPrint(dbg, 0, 20);
#undef DEBUG_HERE
#endif
    }
}


/*
    Gets the total flow rate, and returns it as a null-terminated string, with a suffix for the units.
    
    Note also that this code is intended to be as efficient as possible.
    
    Args: pointer to a buffer to contain the total flow rate, as a null-terminated string
    
    No return code.
*/
void GetGCStatusLoop::GetTotalFlow(char *totalFlow)
{
    GetFlowRate("GTFL", totalFlow);
}

/*
    Gets the total flow rate, and returns it as a floating point value
    
    Args: pointer to a variable to contain the total flow rate, as a floating point value
    
    No return code.
*/
void GetGCStatusLoop::GetTotalFlow(float *totalFlow)
{
    char buff[40];
    GetTotalFlow(buff);
    if(buff[0] == '*') {
        // Assume "EPKT" returned from GC
        *totalFlow = 0.0;
        return;
    }
    
    // 'else' we received a valid value
    buff[4] = '\0'; // Remove the units suffix
    sscanf(buff, "%f", totalFlow);
}

/*
    Gets the column flow rate, and returns it as a null-terminated string, with a suffix for the units.
    
    Args: pointer to a buffer to contain the total flow rate, as a null-terminated string
    
    No return code.
*/
void GetGCStatusLoop::GetColumnFlow(char *columnFlow)
{
    GetFlowRate("GCFL", columnFlow);
}    

/*
    Gets the column flow rate, and returns it as a floating point value
    
    Args: pointer to a variable to contain the column flow rate, as a floating point value
    
    No return code.
*/
void GetGCStatusLoop::GetColumnFlow(float *columnFlow)
{
    char buff[40];
    GetColumnFlow(buff);
    if(buff[0] == '*') {
        // Assume "EPKT" returned from GC
        *columnFlow = 0.0;
        return;
    }
    
    // 'else' we received a valid value
    buff[4] = '\0'; // Remove the units suffix
    sscanf(buff, "%f", columnFlow);
}

/*
    Gets the split flow rate, and returns it as a floating point value
    
    Args: pointer to a variable to contain the split flow rate, as a floating point value
    
    No return code.
*/
void GetGCStatusLoop::GetSplitFlow(float *splitFlow)
{
    float totalFlow;
    float columnFlow;
    
    GetTotalFlow(&totalFlow);
    GetColumnFlow(&columnFlow);
    
    *splitFlow = totalFlow - columnFlow;
}

/*
    Gets the split flow rate, and returns it (to a precision of one decimal place,
    like most floating point values returned by the GC - and with a suffix 
    representing the units) as a null terminated string.
    
    Args: pointer to a buffer to contain the split flow rate, as a null terminated string
    
    No return code.
*/
void GetGCStatusLoop::GetSplitFlow(char *splitFlow)
{
    float flt;
    GetSplitFlow(&flt);
    sprintf(splitFlow, "%.1f ml/min", flt);
}

/*
    Gets the split ratio, and returns it as a floating point value
    
    Args: pointer to a variable to contain the split ratio, as a floating point value
    
    No return code.
*/
void GetGCStatusLoop::GetSplitRatio(float *splitRatio)
{
    float splitFlow;
    float columnFlow;
    
    GetSplitFlow(&splitFlow);
    GetColumnFlow(&columnFlow);
    
    // Avoid divide by zero (and avoid floating point rounding errors)
    if(columnFlow > FLT_MIN) {
        *splitRatio = splitFlow / columnFlow;
    } else {
        *splitRatio = 1.0F; // Default (we have to assign *something*)
    }
}

/*
    Gets the split ratio, and returns it (to a precision of one decimal place,
    like most floating point values returned by the GC) as a null terminated string.
    
    Args: pointer to a buffer to contain the split flow rate, as a null terminated string
    
    No return code.
*/
void GetGCStatusLoop::GetSplitRatio(char *splitRatio)
{
    float flt;
    GetSplitRatio(&flt);
    sprintf(splitRatio, "%.1f", flt);
}

/*
    Gets the number of injections (i.e. runs) since the liner was changed, 
    and returns it as a null-terminated string.
    
    This involves getting both values from the settings stored in QSPI memory.
    
    Args: pointer to a buffer to contain the injection count, as a null-terminated string
          
    No return code.
*/
void GetGCStatusLoop::GetInjectionCountSinceLinerChanged(char *injCount)
{
    int runCount = SettingsHandler::GetIntegerValueFromQSPISettings("RunCount", 0);
    int linerChangedRunCount = SettingsHandler::GetIntegerValueFromQSPISettings("RunCountWhenLinerChanged", 0);

    sprintf(injCount, "%d", (runCount - linerChangedRunCount));
}

/*
    Gets the number of injections (i.e. runs) since the septa was changed, 
    and returns it as a null-terminated string.
    
    This involves getting both values from the settings stored in QSPI memory.
    
    Args: pointer to a buffer to contain the injection count, as a null-terminated string
          
    No return code.
*/
void GetGCStatusLoop::GetInjectionCountSinceSeptaChanged(char *injCount)
{
    int runCount = SettingsHandler::GetIntegerValueFromQSPISettings("RunCount", 0);
    int septaChangedRunCount = SettingsHandler::GetIntegerValueFromQSPISettings("RunCountWhenSeptaChanged", 0);

    sprintf(injCount, "%d", (runCount - septaChangedRunCount));
}

/*
    Gets the number of injections (i.e. runs) since the O-ring was changed, 
    and returns it as a null-terminated string.
    
    This involves getting both values from the settings stored in QSPI memory.
    
    Args: pointer to a buffer to contain the injection count, as a null-terminated string
          
    No return code.
*/
void GetGCStatusLoop::GetInjectionCountSinceOringChanged(char *injCount)
{
    int runCount = SettingsHandler::GetIntegerValueFromQSPISettings("RunCount", 0);
    int oRingChangedRunCount = SettingsHandler::GetIntegerValueFromQSPISettings("RunCountWhenOringChanged", 0);

    sprintf(injCount, "%d", (runCount - oRingChangedRunCount));
}


/*
    Gets the detector type, and returns it as a null-terminated string, with a descriptive prefix.
    (This is the first version of this function, using the "QDTY" command.)
    
    Note also that this code is intended to be as efficient as possible.
    
    Args: pointer to a buffer to contain the detector type, as a null-terminated string
          boolean set true if the caller wants a full prefix ("Detector type: "), 
          or false for a short prefix ("Type: ")
          
    No return code.
*/
void GetGCStatusLoop::GetDetectorTypeOld(char *type, bool wantFullPrefix)
{
    char response[50];
    SetGCDeviceReport("QDTY", response);

    // We expect a response like this: "DDTY0000" for FID, "DDTY0001" for TCD,
    // "DDTY0002" for ECD, "DDTY0003" for PID, "DDTY0004" for PDID, "DDTY0099" for None
    int index = 0;
    if(wantFullPrefix) {
        type[index++] = 'D';
        type[index++] = 'e';
        type[index++] = 't';
        type[index++] = 'e';
        type[index++] = 'c';
        type[index++] = 't';
        type[index++] = 'o';
        type[index++] = 'r';
        type[index++] = ' ';
        type[index++] = 't';
    } else {
        type[index++] = 'T';
    }

    type[index++]  = 'y';
    type[index++]  = 'p';
    type[index++]  = 'e';
    type[index++]  = ':';
    type[index++]  = ' ';

    // Check for "EPKT" first
    if(response[0] == 'E') {
        type[index++] = '*';
        type[index++] = '*';
        type[index++] = ' ';
        type[index++] = 'E';
        type[index++] = 'r';
        type[index++] = 'r';
        type[index++] = 'o';
        type[index++] = 'r';
        type[index++] = ' ';
        type[index++] = '*';
        type[index++] = '*';
        type[index++] = '\0';
    } else {
        switch(response[7] ) {
            case '0':
                type[index++] = 'F';
                type[index++] = 'I';
                type[index++] = 'D';
                type[index++] = '\0';
                break;
            case '1':
                type[index++] = 'T';
                type[index++] = 'C';
                type[index++] = 'D';
                type[index++] = '\0';
                break;
            case '2':
                type[index++] = 'E';
                type[index++] = 'C';
                type[index++] = 'D';
                type[index++] = '\0';
                break;
            case '3':
                type[index++] = 'P';
                type[index++] = 'I';
                type[index++] = 'D';
                type[index++] = '\0';
                break;
            case '4':
                type[index++] = 'P';
                type[index++] = 'D';
                type[index++] = 'I';
                type[index++] = 'D';
                type[index++] = '\0';
                break;
            default:
                type[index++] = 'N';
                type[index++] = 'o';
                type[index++] = 'n';
                type[index++] = 'e';
                type[index++] = '\0';
                break;
        }
    }
}

/*
    Gets the detector type, and returns it as a null-terminated string, with a descriptive prefix.
    (This is the second version of this function, using the new "GDTY" command.)
    
    Note also that this code is intended to be as efficient as possible.
    
    Args: pointer to a buffer to contain the detector type, as a null-terminated string
          boolean set true if the caller wants a full prefix ("Detector type: "), 
          or false for a short prefix ("Type: ")
          boolean set true if the caller wants any prefix, false for no prefix at all
          
    No return code.
*/
void GetGCStatusLoop::GetDetectorType(char *type, bool wantFullPrefix, bool wantPrefix)
{
    char response[50];
    SetGCDeviceReport("GDTY", response);

    // We expect a response like this: "DDTY0000" for FID, "DDTY0001" for TCD,
    // "DDTY0002" for ECD, "DDTY0003" for TXL, "DDTY0005" for NPD, "DDTY0006" for PID,
    // "DDTY0007" for FPD, "DDTY0008" for SPDID. 
    // We assume any other "DDTY00nn" value means "None".
    
    int index = 0;
    if(wantPrefix) {
        if(wantFullPrefix) {
            type[index++] = 'D';
            type[index++] = 'e';
            type[index++] = 't';
            type[index++] = 'e';
            type[index++] = 'c';
            type[index++] = 't';
            type[index++] = 'o';
            type[index++] = 'r';
            type[index++] = ' ';
            type[index++] = 't';
        } else {
            type[index++] = 'T';
        }

        type[index++]  = 'y';
        type[index++]  = 'p';
        type[index++]  = 'e';
        type[index++]  = ':';
        type[index++]  = ' ';
    }
    
    // Check for "EPKT" first
    if(response[0] == 'E') {
        type[index++] = '*';
        type[index++] = '*';
        type[index++] = ' ';
        type[index++] = 'E';
        type[index++] = 'r';
        type[index++] = 'r';
        type[index++] = 'o';
        type[index++] = 'r';
        type[index++] = ' ';
        type[index++] = '*';
        type[index++] = '*';
        type[index++] = '\0';
    } else {
        switch(response[7] ) {
            case '0':
                type[index++] = 'F';
                type[index++] = 'I';
                type[index++] = 'D';
                type[index++] = '\0';
                break;
            case '1':
                type[index++] = 'T';
                type[index++] = 'C';
                type[index++] = 'D';
                type[index++] = '\0';
                break;
            case '2':
                type[index++] = 'E';
                type[index++] = 'C';
                type[index++] = 'D';
                type[index++] = '\0';
                break;
            // Omit 4, i.e. "None" - leave for default
            case '3':
                type[index++] = 'T';
                type[index++] = 'X';
                type[index++] = 'L';
                type[index++] = '\0';
                break;
            case '5':
                type[index++] = 'N';
                type[index++] = 'P';
                type[index++] = 'D';
                type[index++] = '\0';
                break;
            case '6':
                type[index++] = 'P';
                type[index++] = 'I';
                type[index++] = 'D';
                type[index++] = '\0';
                break;
            case '7':
                type[index++] = 'F';
                type[index++] = 'P';
                type[index++] = 'D';
                type[index++] = '\0';
                break;
            case '8':
                type[index++] = 'S';
                type[index++] = 'P';
                type[index++] = 'D';
                type[index++] = 'I';
                type[index++] = 'D';
                type[index++] = '\0';
                break;
            default:
                type[index++] = 'N';
                type[index++] = 'o';
                type[index++] = 'n';
                type[index++] = 'e';
                type[index++] = '\0';
                break;
        }
    }
}

/*
    Gets the detector range, and returns it as a null-terminated string.
    
    Note also that this code is intended to be as efficient as possible.
    
    Args: pointer to a buffer to contain the detector range, as a null-terminated string
          
    No return code.
*/
void GetGCStatusLoop::GetDetectorRange(char *range)
{
    char response[50];
    SetGCDeviceReport("GRNG", response);

    // We expect a response like this: "DRNG0001" for x2, 
    // "DRNG0002" for x10, "DRNG0003" for x100, "DRNG0004" for x1000
    
    int index = 0;
    // Check for "EPKT" first
    if(response[0] == 'E') {
        range[index++] = '*';
        range[index++] = '*';
        range[index++] = ' ';
        range[index++] = 'E';
        range[index++] = 'r';
        range[index++] = 'r';
        range[index++] = 'o';
        range[index++] = 'r';
        range[index++] = ' ';
        range[index++] = '*';
        range[index++] = '*';
    } else {
        range[index++] = 'x';
        switch (response[7]) {
            case '2': 
                range[index++] = '1';
                range[index++] = '0';
                break;
            case '3': 
                range[index++] = '1';
                range[index++] = '0';
                range[index++] = '0';
                break;
            case '4': 
                range[index++] = '1';
                range[index++] = '0';
                range[index++] = '0';
                range[index++] = '0';
                break;
            default:
                range[index++] = '2';
                break;
        }
    }
            
    range[index] = '\0';
}

/*
    Gets the detector ignition state (not lit, igniting, lit), and returns it as a null-terminated string.
    
    Note also that this code is intended to be as efficient as possible.
    
    Args: pointer to a buffer to contain the state, as a null-terminated string
          
    No return code.
*/
void GetGCStatusLoop::GetDetectorIgnitionState(char *state)
{
    char response[50];
    SetGCDeviceReport("QIGN", response);

    // We expect a response like this: "DIGN0001" for "not lit", 
    // "DIGN0002" for "igniting", "DIGN0003" for "lit"
    
    int index = 0;
    // Check for "EPKT" first
    if(response[0] == 'E') {
        state[index++] = '*';
        state[index++] = '*';
        state[index++] = ' ';
        state[index++] = 'E';
        state[index++] = 'r';
        state[index++] = 'r';
        state[index++] = 'o';
        state[index++] = 'r';
        state[index++] = ' ';
        state[index++] = '*';
        state[index++] = '*';
    } else {
        switch (response[7]) {
            case '2': 
                state[index++] = 'i';
                state[index++] = 'g';
                state[index++] = 'n';
                state[index++] = 'i';
                state[index++] = 't';
                state[index++] = 'i';
                state[index++] = 'n';
                state[index++] = 'g';
                break;
            case '3': 
                state[index++] = 'l';
                state[index++] = 'i';
                state[index++] = 't';
                break;
            default:
                state[index++] = 'n';
                state[index++] = 'o';
                state[index++] = 't';
                state[index++] = ' ';
                state[index++] = 'l';
                state[index++] = 'i';
                state[index++] = 't';
                break;
        }
    }
        
    state[index] = '\0';
}

/*
    Gets the specified flow rate, and returns it as a null-terminated string.
    
    Note also that this code is intended to be as efficient as possible.
    
    Args: pointer to the command characters, as a null-terminated string
          pointer to a buffer to contain the flow rate, as a null-terminated string
          
    No return code.
*/
void GetGCStatusLoop::GetFlowRate(char* cmd, char *rate)
{
    char response[50];
    SetGCDeviceReport(cmd, response);

    // We expect a response like this: "Dxxxnnnn", where 'nnnn' is the flow rate, in ml/min.
    // Note that we do *not* want leading zeroes
    
    int index = 0;
    // Check for "EPKT" first
    if(response[0] == 'E') {
        rate[index++] = '*';
        rate[index++] = '*';
        rate[index++] = ' ';
        rate[index++] = 'E';
        rate[index++] = 'r';
        rate[index++] = 'r';
        rate[index++] = 'o';
        rate[index++] = 'r';
        rate[index++] = ' ';
        rate[index++] = '*';
        rate[index++] = '*';
    } else {
        bool wantNextChars = false;
        if(response[4] != '0') {
            rate[index++] = response[4];
            wantNextChars = true;
        }
        if(wantNextChars || (response[5] != '0')) {
            rate[index++] = response[5];
            wantNextChars = true;
        }
        if(wantNextChars || (response[6] != '0')) {
            rate[index++] = response[6];
        }
        // We always want the final char, even if it's zero - 
        // the flow rate may actually be zero
        rate[index++] = response[7];
        
        rate[index++] = ' ';
        rate[index++] = 'm';
        rate[index++] = 'l';
        rate[index++] = '/';
        rate[index++] = 'm';
        rate[index++] = 'i';
        rate[index++] = 'n';
    }
        
    rate[index] = '\0';
}

/*
    Gets the detector fuel flow rate, and returns it as a null-terminated string.
    
    Args: pointer to a buffer to contain the fuel flow rate, as a null-terminated string
          
    No return code.
*/
void GetGCStatusLoop::GetFuelFlowRate(char *rate)
{
    GetFlowRate("GHFL", rate);
}

/*
    Gets the detector air flow rate, and returns it as a null-terminated string.
    
    Args: pointer to a buffer to contain the air flow rate, as a null-terminated string
          
    No return code.
*/
void GetGCStatusLoop::GetAirFlowRate(char *rate)
{
    GetFlowRate("GAFL", rate);
}



/*
    If the detector temperature has changed on the detector page,
    redraw it manually - do not redisplay the entire page just for this
    (trying to reduce "flashing and banging")
*/
void GetGCStatusLoop::DrawDetectorTemperatureVariableOnDetectorPage(void)
{
    // Hard coded values taken from easyGUI -
    // Note that the temperature is at the same coordinates on all the detector pages
    //(except TCD, where it does not appear)
    RedrawSingleEasyGUIVariableOnComponentPage(380, 290, &GuiVar_detectorTemperature, GuiLib_ALIGN_LEFT, DETECTOR);
}

/*
    If the detector target temperature has changed on the detector page,
    redraw it manually - do not redisplay the entire page just for this
    (trying to reduce "flashing and banging")
*/
void GetGCStatusLoop::DrawDetectorTargetTemperatureVariableOnDetectorPage(void)
{
    // Hard coded values taken from easyGUI -
    // Note that the target temperature is at the same coordinates on all the detector pages
    //(except TCD, where it does not appear)
    RedrawSingleEasyGUIVariableOnComponentPage(380, 330, &GuiVar_detectorTargetTemperature2, GuiLib_ALIGN_LEFT, DETECTOR);
}

/*
    Displays the data on one of the detector pages, by copying the data to the relevant easyGUI variables,
    and calling the relevant functions to actually display it.
    
    Args: a boolean specifying whether or not the display is actually to be updated.
          Even if it is false when we are called, we may set it true if we discover
          one or more data items has changed - but note that we will not set it false 
          if it is already true. If it is true after we have looked at all 
          the home page data, we will then update the display. (If we are called
          with this value set to false, the caller is effectively saying
          'display the detector page data only if it has changed').
          
          the detector type. (It seems ridiculous to have separate functions for each type,
          since the values that need to be displayed are mostly the same.)
           
          the page number (this is derived from the detector type - if the caller knows the detector type,
          it must also (in effect) know the page number, so it seems ridiculous for this function 
          to work it out again).
          
    No return code.
*/
void GetGCStatusLoop::DisplayDetectorPageData(bool mustUpdateDisplay, DetectorType detectorType, int pageNumber)
{
//    EasyGUIDebugPrint("Detector Page", 100, 100);
    // Detector temperature and type
    char buff[40];
    
    if(detectorType != NO_DETECTOR) {
        GetDetectorType(buff, false, false);
        if(strcmp(buff, GuiVar_detectorType2) != 0) {
            strcpy(GuiVar_detectorType2, buff);
            mustUpdateDisplay = true;
        }
        
        GetDetectorRange(buff);
        if(strcmp(buff, GuiVar_detectorRange) != 0) {
            strcpy(GuiVar_detectorRange, buff);
            mustUpdateDisplay = true;
        }
            
        GetFuelFlowRate(buff);    
        if(strcmp(buff, GuiVar_detectorFuelFlowRate) != 0) {
            strcpy(GuiVar_detectorFuelFlowRate, buff);
            mustUpdateDisplay = true;
        }
        
        GetAirFlowRate(buff);    
        if(strcmp(buff, GuiVar_detectorAirFlowRate) != 0) {
            strcpy(GuiVar_detectorAirFlowRate, buff);
            mustUpdateDisplay = true;
        }
        
        GetDetectorIgnitionState(buff);
        if(strcmp(buff, GuiVar_detectorStatus) != 0) {
            strcpy(GuiVar_detectorStatus, buff);
            mustUpdateDisplay = true;
        }
        
        // Now - variables that are different between the detector types
        if(detectorType != TCD_DETECTOR) {
            
            GetDetectorTemperature(buff);
            if(strcmp(buff, GuiVar_detectorTemperature) != 0) {
                strcpy(GuiVar_detectorTemperature, buff);

                //mustUpdateDisplay = true;
                // No - just do this (don't force entire rectangle to redisplay)
                DrawDetectorTemperatureVariableOnDetectorPage();
            }
            
            GetDetectorTargetTemperature(buff, stringFormatdegCUnits);
            if(strcmp(buff, GuiVar_detectorTargetTemperature2) != 0) {
                strcpy(GuiVar_detectorTargetTemperature2, buff);

                //mustUpdateDisplay = true;
                // No - just do this (don't force entire rectangle to redisplay)
                DrawDetectorTemperatureVariableOnDetectorPage();
            }
        }
        
        if(detectorType == TCD_DETECTOR) {
            
            GetTCDDetectorFilamentTemperature(buff);
            if(strcmp(buff, GuiVar_tcdDetectorFilamentTemperature) != 0) {
                strcpy(GuiVar_tcdDetectorFilamentTemperature, buff);
                mustUpdateDisplay = true;
            }
            
            GetTCDDetectorFilamentPolarity(buff);
            if(strcmp(buff, GuiVar_tcdDetectorFilamentPolarity) != 0) {
                strcpy(GuiVar_tcdDetectorFilamentPolarity, buff);
                mustUpdateDisplay = true;
            }
            
        } else if(detectorType == ECD_DETECTOR) {
            
            GetECDDetectorCurrent(buff);
            if(strcmp(buff, GuiVar_ecdDetectorCurrent) != 0) {
                strcpy(GuiVar_ecdDetectorCurrent, buff);
                mustUpdateDisplay = true;
            }

        } else if(detectorType == FPD_DETECTOR) {
            
            GetFPDDetectorSensitivity(buff);
            if(strcmp(buff, GuiVar_fpdDetectorSensitivity) != 0) {
                strcpy(GuiVar_fpdDetectorSensitivity, buff);
                mustUpdateDisplay = true;
            }
        }
    }
    
    if(SinglePageGCComponentStatusHasChanged(DETECTOR, lastDetectorStatusDisplayedOnDetectorPage)) {
        mustUpdateDisplay = true;
    }

    if(mustUpdateDisplay) {

#ifdef WANT_DETECTOR_STATUS_RECTANGLE
        // Reduce display flickering - get the component status from the GC
        // *before* we call GuiLib_Clear()
        if(singleGCComponentPageStatusColorAreas != NULL) {
            UpdateSingleGCComponentPageStatusColorArea(DETECTOR);
        }
#endif
        
#ifndef WANT_DETECTOR_STATUS_RECTANGLE
        // Makes the display flicker - but omitting it means old text is not cleared from the display
#define WANT_GUILIB_CLEAR
#endif

#ifdef WANT_GUILIB_CLEAR
#ifdef USING_BACKGROUND_BITMAP
        DrawBackgroundBitmap(); // 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
#else
        GuiLib_Clear();
#endif
#undef WANT_GUILIB_CLEAR
#endif
        //...except that redrawing the status rectangle effectively clears the text on top of the rectangle -
        // so we do not need GuiLib_Clear here, since all the detector page data appears on top of the rectangle. 
        // Without it, only the text flickers, not the rectangle

#ifdef WANT_DETECTOR_STATUS_RECTANGLE
        // Note - we draw the status rectangle 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
        if(singleGCComponentPageStatusColorAreas != NULL) {
            singleGCComponentPageStatusColorAreas->DisplayGCComponentStatus(DETECTOR);
            
            lastDetectorStatusDisplayedOnDetectorPage = singleGCComponentPageStatusColorAreas->GetGCComponentStatus(DETECTOR);
        }
#endif
        
        // And (currently) we draw the component bitmap on top of the rectangle
        if(qspiBitmaps != NULL) {
            qspiBitmaps->DisplayDetectorComponentBitmap();
        }

        GuiLib_ShowScreen(pageNumber, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);
    
        GuiLib_Refresh();    

#define DEBUG_HERE
#ifdef DEBUG_HERE
    char dbg[100];
    sprintf(dbg, "After GuiLib_Clear 4");
    EasyGUIDebugPrint(dbg, 0, 20);
#undef DEBUG_HERE
#endif
    }
}

/*
    Gets the gas control mode, and returns it as a null-terminated string, with a descriptive prefix.
    
    Note also that this code is intended to be as efficient as possible.
    
    Args: pointer to a buffer to contain the mode, as a null-terminated string
          boolean set true if the caller wants a full prefix ("Gas control mode: "), 
          or false for a short prefix ("Control mode: ")
          boolean set true if the caller wants a prefix, false for no prefix whatsoever
          
    Returns true if the mode was obtained successfully, false if there was an error.
*/
bool GetGCStatusLoop::GetGasControlMode(char *mode, bool wantFullPrefix, bool wantAnyPrefix)
{
    bool retval = true; // Return false only if we get "EPKT" from the GC
    
    char response[50];
//    SetGCDeviceReport("QGAS", response);
//    // We expect a response like this: "DGAS0000" for Manual, "DGAS0001" for EPPC, or "EPKT" for error
// Above command has been removed - now use:
    SetGCDeviceReport("GGTY", response);
    // We expect a response like this: "DGTY0000" for Manual, "DGTY0001" for EPPC, or "EPKT" for error

    int index = 0;
    if(wantAnyPrefix) {
        if(wantFullPrefix) {
            mode[index++] = 'G';
            mode[index++] = 'a';
            mode[index++] = 's';
            mode[index++] = ' ';
            mode[index++] = 'c';
        } else {
            mode[index++] = 'C';
        }
    
        mode[index++]  = 'o';
        mode[index++]  = 'n';
        mode[index++]  = 't';
        mode[index++]  = 'r';
        mode[index++]  = 'o';
        mode[index++]  = 'l';
        mode[index++]  = ' ';
        mode[index++]  = 'm';
        mode[index++]  = 'o';
        mode[index++]  = 'd';
        mode[index++]  = 'e';
        mode[index++]  = ':';
        mode[index++]  = ' ';
    }
    
    if(response[0] == 'E') {
        mode[index++]  = '*';
        mode[index++]  = '*';
        mode[index++]  = ' ';
        mode[index++]  = 'E';
        mode[index++]  = 'r';
        mode[index++]  = 'r';
        mode[index++]  = 'o';
        mode[index++]  = 'r';
        mode[index++]  = ' ';
        mode[index++]  = '*';
        mode[index++]  = '*';
        mode[index++] = '\0';
        
        retval = false;
    } else {
        if(response[7] == '0') {
            mode[index++] = 'M';
            mode[index++] = 'a';
            mode[index++] = 'n';
            mode[index++] = 'u';
            mode[index++] = 'a';
            mode[index++] = 'l';
            mode[index++] = '\0';
        } else {
            mode[index++] = 'E';
            mode[index++] = 'P';
            mode[index++] = 'P';
            mode[index++] = 'C';
            mode[index++] = '\0';
        }    
    }
    
    return retval;
}

/*
    Gets the carrier gas type, and returns it as a null-terminated string, with a descriptive prefix.
    
    Note also that this code is intended to be as efficient as possible.
    
    Args: pointer to the buffer to contain the carrier gas type, as a null-terminated string
          
    No return code.
*/
void GetGCStatusLoop::GetCarrierGasType(char *type)
{
    char response[50];
    SetGCDeviceReport("GCGS", response);
    // We expect a response like this: "DCGS000n", where 'n' = 0 for nitrogen, 1 for helium, and 2 for hydrogen,
    // or "EPKT" for error
    
    int index = 0;
    if(response[0] == 'E') {
        type[index++]  = '*';
        type[index++]  = '*';
        type[index++]  = ' ';
        type[index++]  = 'E';
        type[index++]  = 'r';
        type[index++]  = 'r';
        type[index++]  = 'o';
        type[index++]  = 'r';
        type[index++]  = ' ';
        type[index++]  = '*';
        type[index++]  = '*';
    } else {
        switch(response[7]) {
            case '1':
                type[index++] = 'H';
                type[index++] = 'e';
                type[index++] = 'l';
                type[index++] = 'i';
                type[index++] = 'u';
                type[index++] = 'm';
                break;
            case '2':
                type[index++] = 'H';
                type[index++] = 'y';
                type[index++] = 'd';
                type[index++] = 'r';
                type[index++] = 'o';
                type[index++] = 'g';
                type[index++] = 'e';
                type[index++] = 'n';
                break;
            default:
                type[index++] = 'N';
                type[index++] = 'i';
                type[index++] = 't';
                type[index++] = 'r';
                type[index++] = 'o';
                type[index++] = 'g';
                type[index++] = 'e';
                type[index++] = 'n';
                break;
        }
    }

    type[index++] = '\0';
}

/*
    Gets the date the filter was last changed, and returns it as a null-terminated string, with a descriptive prefix.
    
    Note also that this code is intended to be as efficient as possible.
    
    Args: pointer to the buffer to contain the date the filter was changed, as a null-terminated string
          
    No return code.
    
    ** This function is currently a placeholder until we work out how to implement this facility **
*/
void GetGCStatusLoop::GetGasFilterDateChanged(char *date)
{
    // TODO: Fill in, i.e. get the date from the GC somehow
    int index = 0;
    date[index++] = '0';
    date[index++] = '1';
    date[index++] = '/';
    date[index++] = '0';
    date[index++] = '1';
    date[index++] = '/';
    date[index++] = '2';
    date[index++] = '0';
    date[index++] = '1';
    date[index++] = '6';
    date[index]   = '\0';
}


/*
    Displays the data on the gas page, by copying it to the relevant easyGUI variables,
    and calling the relevant functions to actually display it.
    
    Args: a boolean specifying whether or not the display is actually to be updated.
          Even if it is false when we are called, we may set it true if we discover
          one or more data items has changed - but note that we will not set it false 
          if it is already true. If it is true after we have looked at all 
          the home page data, we will then update the display. (If we are called
          with this value set to false, the caller is effectively saying
          'display the gas page data only if it has changed').
          
    No return code.
*/
void GetGCStatusLoop::DisplayGasInformationPageData(bool mustUpdateDisplay)
{
    //EasyGUIDebugPrint("Gas Page", 100, 100);
    // Gas pressure and control mode
    char buff[60];
    
    GetGasPressure(buff, false, true);
    if(strcmp(buff, GuiVar_gasPressure) != 0) {
        strcpy(GuiVar_gasPressure, buff);

        //mustUpdateDisplay = true;
        // No - just do this (don't force entire rectangle to redisplay)
        // Hard coded values taken from easyGUI
        RedrawSingleEasyGUIVariableOnComponentPage(430, 90, &GuiVar_gasPressure, GuiLib_ALIGN_LEFT, GAS);
    }

    GetGasPulsedPressure(buff);
    if(strcmp(buff, GuiVar_gasPulsedPressure) != 0) {
        strcpy(GuiVar_gasPulsedPressure, buff);

        //mustUpdateDisplay = true;
        // No - just do this (don't force entire rectangle to redisplay)
        // Hard coded values taken from easyGUI
        RedrawSingleEasyGUIVariableOnComponentPage(430, 130, &GuiVar_gasPulsedPressure, GuiLib_ALIGN_LEFT, GAS);
    }

    GetGasControlMode(buff, true, false);
    if(strcmp(buff, GuiVar_gasControlMode2) != 0) {
        strcpy(GuiVar_gasControlMode2, buff);

        //mustUpdateDisplay = true;
        // No - just do this (don't force entire rectangle to redisplay)
        // Hard coded values taken from easyGUI
        RedrawSingleEasyGUIVariableOnComponentPage(430, 170, &GuiVar_gasControlMode2, GuiLib_ALIGN_LEFT, GAS);
    }
    
    GetCarrierGasType(buff);
    if(strcmp(buff, GuiVar_gasCarrierType) != 0) {
        strcpy(GuiVar_gasCarrierType, buff);

        //mustUpdateDisplay = true;
        // No - just do this (don't force entire rectangle to redisplay)
        // Hard coded values taken from easyGUI
        RedrawSingleEasyGUIVariableOnComponentPage(430, 210, &GuiVar_gasCarrierType, GuiLib_ALIGN_LEFT, GAS);
    }

    GetGasFilterDateChanged(buff);
    if(strcmp(buff, GuiVar_gasFilterDateChanged) != 0) {
        strcpy(GuiVar_gasFilterDateChanged, buff);

        //mustUpdateDisplay = true;
        // No - just do this (don't force entire rectangle to redisplay)
        // Hard coded values taken from easyGUI
        RedrawSingleEasyGUIVariableOnComponentPage(430, 270, &GuiVar_gasFilterDateChanged, GuiLib_ALIGN_LEFT, GAS);
    }

    if(SinglePageGCComponentStatusHasChanged(GAS, lastGasStatusDisplayedOnGasInformationPage)) {
        mustUpdateDisplay = true;
    }

    if(mustUpdateDisplay) {

#ifdef WANT_GAS_STATUS_RECTANGLE
        // Reduce display flickering - get the component status from the GC
        // *before* we call GuiLib_Clear()
        if(singleGCComponentPageStatusColorAreas != NULL) {
            UpdateSingleGCComponentPageStatusColorArea(GAS);
        }
#endif
        
#ifndef WANT_GAS_STATUS_RECTANGLE
        // Makes the display flicker - but omitting it means old text is not cleared from the display
#define WANT_GUILIB_CLEAR
#endif

#ifdef WANT_GUILIB_CLEAR
#ifdef USING_BACKGROUND_BITMAP
        DrawBackgroundBitmap(); // 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
#else
        GuiLib_Clear();
#endif
#undef WANT_GUILIB_CLEAR
#endif
        //...except that redrawing the status rectangle effectively clears the text on top of the rectangle -
        // so we do not need GuiLib_Clear here, since all the gas page data appears on top of the rectangle. 
        // Without it, only the text flickers, not the rectangle

#ifdef WANT_GAS_STATUS_RECTANGLE
        // Note - we draw the status rectangle 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
        if(singleGCComponentPageStatusColorAreas != NULL) {
            singleGCComponentPageStatusColorAreas->DisplayGCComponentStatus(GAS);

            lastGasStatusDisplayedOnGasInformationPage = singleGCComponentPageStatusColorAreas->GetGCComponentStatus(GAS);
        }
#endif
        
        // And (currently) we draw the component bitmap on top of the rectangle
        if(qspiBitmaps != NULL) {
            qspiBitmaps->DisplayGasComponentBitmap();
        }

        GuiLib_ShowScreen(GuiStruct_GasInformationPage_6, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);
    
        GuiLib_Refresh();    

#define DEBUG_HERE
#ifdef DEBUG_HERE
    char dbg[100];
    sprintf(dbg, "After GuiLib_Clear 5");
    EasyGUIDebugPrint(dbg, 0, 20);
#undef DEBUG_HERE
#endif
    }
}


/*
    Public function, allowing an external caller to tell us 
    to display, or not, the ramp scroll buttons on the Gas Method page.
    
    Assume that the caller knows the Injector Method page *is*
    currently being displayed.
*/
void GetGCStatusLoop::ShowGasMethodPageScrollButtonsIfNecessary(void)
{
    ShowMethodPageScrollButtonsIfNecessary(gasMethodRampData);
}

void GetGCStatusLoop::ScrollGasMethodRampsUpIfPossible(void)
{
    if(currentPage == GuiStruct_GasMethodPage_Def) {
        if(gasMethodPageScrollIndex > 0) {
            --gasMethodPageScrollIndex;
                
            DisplayGasMethodPageData(false);
        }
    }
}

void GetGCStatusLoop::ScrollGasMethodRampsDownIfPossible(void)
{
    if(currentPage == GuiStruct_GasMethodPage_Def) {
        if(gasMethodRampData != NULL) {
            if(gasMethodPageScrollIndex < gasMethodRampData->GetScrollRange()) {
                ++gasMethodPageScrollIndex;
                
                DisplayGasMethodPageData(false);
            }
        }
    }
}

/*
    Displays the data on the Gas Method page, by copying it to the relevant easyGUI variables,
    and calling the relevant functions to actually display it.
    
    Args: a boolean specifying whether or not the display is actually to be updated.
          Even if it is false when we are called, we may set it true if we discover
          one or more data items has changed - but note that we will not set it false 
          if it is already true. If it is true after we have looked at all 
          the home page data, we will then update the display. (If we are called
          with this value set to false, the caller is effectively saying
          'display the column page data only if it has changed').
          
    No return code.
*/
void GetGCStatusLoop::DisplayGasMethodPageData(bool mustUpdateDisplay)
{
    char buff[40];
    
    GetGasPressure(buff, false, false);
    if(strcmp(buff, GuiVar_gasMethodInitialPressure) != 0) {
        strcpy(GuiVar_gasMethodInitialPressure, buff);

        //mustUpdateDisplay = true;
        // No - just do this (don't force entire page to redisplay)
        // Hard coded values taken from easyGUI
        RedrawSingleEasyGUIVariableOnComponentPage(520, 85, &GuiVar_gasMethodInitialPressure, GuiLib_ALIGN_RIGHT, COLUMN);
    }

    GetInitialHoldTime(buff);
    if(strcmp(buff, GuiVar_gasMethodInitialHold) != 0) {
        strcpy(GuiVar_gasMethodInitialHold, buff);

        //mustUpdateDisplay = true;
        // No - just do this (don't force entire page to redisplay)
        // Hard coded values taken from easyGUI
        RedrawSingleEasyGUIVariableOnComponentPage(520, 120, &GuiVar_gasMethodInitialHold, GuiLib_ALIGN_RIGHT, COLUMN);
    }

    if(gasMethodRampData == NULL) {
        gasMethodRampData = new GasMethodRampData(usbDevice, usbHostGC);
    }
    
    if(gasMethodRampData != NULL) {
        if(!gasMethodRampData->GotRampData()) {
            gasMethodRampData->GetRampDataFromGC();
        }
        
        sprintf(buff, "%u", gasMethodRampData->GetRampCount());
        if(strcmp(buff, GuiVar_gasMethodRampCount) != 0) {
            strcpy(GuiVar_gasMethodRampCount, buff);
    
            //mustUpdateDisplay = true;
            // No - just do this (don't force entire page to redisplay)
            // Hard coded values taken from easyGUI
            RedrawSingleEasyGUIVariableOnComponentPage(520, 155, &GuiVar_gasMethodRampCount, GuiLib_ALIGN_RIGHT, COLUMN);
        }
        
        if(gasMethodRampData->NeedToUpdateEasyGUIMethodPageRampVariables()) {
            
            gasMethodPageScrollIndex = 0;
            
            gasMethodRampData->UpdateEasyGUIMethodPageVariables(gasMethodPageScrollIndex);
            
            previousGasMethodPageScrollIndex = gasMethodPageScrollIndex;
            
            mustUpdateDisplay = true; // Not practical to write all these variables individually

        } else if (previousGasMethodPageScrollIndex != gasMethodPageScrollIndex) {

            gasMethodRampData->UpdateEasyGUIMethodPageVariables(gasMethodPageScrollIndex);
            
            previousGasMethodPageScrollIndex = gasMethodPageScrollIndex;
            
            mustUpdateDisplay = true; // Not practical to write all these variables individually
        }
    }
    
    if(SinglePageGCComponentStatusHasChanged(GAS)) {
        mustUpdateDisplay = true;
    }

    if(mustUpdateDisplay) {

#ifdef WANT_INJECTOR_STATUS_RECTANGLE        
        // Reduce display flickering - get the component status from the GC
        // *before* we call GuiLib_Clear()
        if(singleGCComponentPageStatusColorAreas != NULL) {
            UpdateSingleGCComponentPageStatusColorArea(GAS);
        }
#endif       

#ifndef WANT_INJECTOR_STATUS_RECTANGLE
#define WANT_GUILIB_CLEAR
#endif

#ifdef WANT_GUILIB_CLEAR
#ifdef USING_BACKGROUND_BITMAP
        DrawBackgroundBitmap(); // 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
#else
        GuiLib_Clear();
#endif
#undef WANT_GUILIB_CLEAR
#endif
        //...except that redrawing the status rectangle effectively clears the text on top of the rectangle -
        // so we do not need GuiLib_Clear here, since all the column page data appears on top of the rectangle. 
        // Without it, only the text flickers, not the rectangle

#ifdef WANT_INJECTOR_STATUS_RECTANGLE
        // Note - we draw the status rectangle 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
        if(singleGCComponentPageStatusColorAreas != NULL) {
            singleGCComponentPageStatusColorAreas->DisplayGCComponentStatus(GAS);
        }
#endif

        // And (currently) we draw the component bitmap on top of the rectangle
        if(qspiBitmaps != NULL) {
            qspiBitmaps->DisplayGasComponentBitmap();
        }

        GuiLib_ShowScreen(GuiStruct_GasMethodPage_Def, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);
    
        ShowMethodPageScrollButtonsIfNecessary(gasMethodRampData);

        GuiLib_Refresh();    
    }
}


/*
    Gas pressure profile X axis units may be minutes or seconds.
    Set up the label easyGUI variable appropriately.
*/
void GetGCStatusLoop::SetupGasFlowProfilePageXAxisLabel(TimeUnit timeUnit)
{
    if(timeUnit == SECONDS) {
        strcpy(GuiVar_gasFlowProfilePageXAxisLabel, "Time (seconds)");
    } else {
        strcpy(GuiVar_gasFlowProfilePageXAxisLabel, "Time (minutes)");
    }
}

/*
    Sets up the data for the graph on the Gas Flow Profile page,
    and causes it to be displayed
*/
void GetGCStatusLoop::DisplayGasFlowProfilePageGraph(void)
{
    // Test values only:
    // - dataset 0 is a line representing the flow profile of the run from 'now' to the finish
    // - dataset 1 is a bar chart representing the flow profile of the run from 'now' to the finish
    // - dataset 2 is a line representing the flow profile from the start to 'now'
    // - dataset 3 is a bar chart representing the flow profile of the run from the start to 'now'
    // - dataset 4 is a single dot at the current time and temperature
    
    TimeUnit timeUnit = MINUTES;
    if(gasFlowProfilePageGraphCompleteProfileDataSet->GetTotalMethodTime() < methodTimeUnitsThreshold) {
        timeUnit = SECONDS;
    }
    
    const float yAxisScaleFactor = 10.0f;

    // Dataset 0
    gasFlowProfilePageGraph->SetDataForGraphDataSetInTenthsOfMinutes(0, yAxisScaleFactor, gasFlowProfilePageGraphDataSet0);
    
    // Dataset 1 
    gasFlowProfilePageGraph->SetDataForGraphDataSetInTenthsOfMinutes(1, yAxisScaleFactor, gasFlowProfilePageGraphDataSet1);
    
    // Dataset 2
    gasFlowProfilePageGraph->SetDataForGraphDataSetInTenthsOfMinutes(2, yAxisScaleFactor, gasFlowProfilePageGraphDataSet2);
    
    // Dataset 3
    gasFlowProfilePageGraph->SetDataForGraphDataSetInTenthsOfMinutes(3, yAxisScaleFactor, gasFlowProfilePageGraphDataSet3);
    
    // Dataset 4
    gasFlowProfilePageGraph->SetDataForGraphDataSetInTenthsOfMinutes(4, yAxisScaleFactor, gasFlowProfilePageGraphDataSet4);
    
    gasFlowProfilePageGraph->SetXAxisUnits(timeUnit);
    
    if(timeUnit == SECONDS) {
        strcpy(GuiVar_gasFlowProfilePageXAxisLabel, "Time (seconds)");
    } else {
        strcpy(GuiVar_gasFlowProfilePageXAxisLabel, "Time (minutes)");
    }
    
    
    if(gasFlowProfilePageGraphCompleteProfileDataSet->GetPointCount() == 0) {
        strcpy(GuiVar_gasFlowProfilePageNoMethod, "No method set up");
    } else {
        GuiVar_gasFlowProfilePageNoMethod[0] = '\0';
    }

    // The tick sizes must match those set in easyGUI - we do not seem 
    // to be able to get them at runtime
    GuiConst_INT32S minX, maxX;
    //GuiConst_INT32S tickSize = (timeUnit == SECONDS) ? 300 : 100;
    // Always in 1/10 minutes
    GuiConst_INT32S xAxisTickSize = (timeUnit == SECONDS) ? 5 : 100; // 5 == 0.5 minutes (i.e. 30 seconds), 100 == 10 minutes
    gasFlowProfilePageGraphCompleteProfileDataSet->GetXAxisRangeInTenthsOfMinutes(&minX, &maxX, xAxisTickSize);
    gasFlowProfilePageGraph->SetXAxisRange(0, maxX); // Always start X axis at zero
    
    GuiConst_INT32S minY, maxY;
    GuiConst_INT32S yAxisTickSize = 10;
    gasFlowProfilePageGraphCompleteProfileDataSet->GetYAxisRange(&minY, &maxY, yAxisTickSize);
    gasFlowProfilePageGraph->SetYAxisRange(0, (maxY * yAxisScaleFactor)); // Always start Y axis at zero
    
    
    gasFlowProfilePageGraph->DrawAxes();

    // We need to draw the X axis labels ourselves, since our time values are in units of 0.1 minute,
    // but easyGUI graphs work only in integers - we therefore have to multiply the time values by 10
    // before passing them to easyGUI, and if we let easyGUI draw its own values on the X axis, 
    // they would be 10 times the correct values.
    // Graph coordinates copied from easyGUI - I cannot find a way of obtaining them at runtime.
    if(timeUnit == SECONDS) {
        gasFlowProfilePageGraph->DrawXAxisLabels(0, (maxX * 6), (xAxisTickSize * 6), 150, 340, 500);
    } else {
        gasFlowProfilePageGraph->DrawXAxisLabels(0, (maxX / 10), (xAxisTickSize / 10), 150, 340, 500);
    }
    // Note that we repeat this call by calling 'DrawXAxisLabels' without arguments 
    // every time we (re)display this page

    // Similar to the X axis - the values we pass to the easyGUI graph are scaled to be larger
    // than the real values, to get round the fact that easyGUI graphs work in integers.
    // (This can cause the profile to appear to be 'stepped', which obviously we do not want.)
    // We must therefore draw the Y axis values ourselves.
    gasFlowProfilePageGraph->DrawYAxisLabels(0, maxY, yAxisTickSize, 150, 340, 250);
    // Note that we repeat this call by calling 'DrawYAxisLabels' without arguments 
    // every time we (re)display this page

    gasFlowProfilePageGraph->DrawDataSet(0);
    gasFlowProfilePageGraph->ShowDataSet(0);

    gasFlowProfilePageGraph->DrawDataSet(1);
    gasFlowProfilePageGraph->ShowDataSet(1);

    gasFlowProfilePageGraph->DrawDataSet(2);
    gasFlowProfilePageGraph->ShowDataSet(2);

    gasFlowProfilePageGraph->DrawDataSet(3);
    gasFlowProfilePageGraph->ShowDataSet(3);

    gasFlowProfilePageGraph->DrawDataSet(4);
    gasFlowProfilePageGraph->ShowDataSet(4);

    gasFlowProfilePageGraph->Redraw();
    
#ifdef TEST_GUILIB_VLINE_PROFILES
    // *** TESTING *** Draw the profile as a solid colour, direct to the display
    //                 Coords manually copied from easyGUI application.
    //                 Boundary between colours is arbitrary for now
    GuiConst_INTCOLOR graphColour1 = SixteenBitColorValue(100, 100, 100);
    GuiConst_INTCOLOR graphColour2 = SixteenBitColorValue(200, 200, 200);
//    double colourBoundaryX = (double) gasFlowProfilePageGraphCompleteProfileDataSet->GetTotalMethodTime() / 5.0; // Should be one-fifth across the profile
    double colourBoundaryX = -1.0; // No - use one colour only
    if(timeUnit == SECONDS) {
        gasFlowProfilePageGraphCompleteProfileDataSet->DrawUsingGuiLibVLine(160, 330, ((double) 500 / (double) (maxX * 6)), ((double) -250 / (double) maxY), graphColour1, graphColour2, colourBoundaryX);
    } else {
        gasFlowProfilePageGraphCompleteProfileDataSet->DrawUsingGuiLibVLine(160, 330, ((double) 500 / (double) (maxX / 10)), ((double) -250 / (double) maxY), graphColour1, graphColour2, colourBoundaryX);
    }
#endif // TEST_GUILIB_VLINE_PROFILES
}

void GetGCStatusLoop::DisplayGasFlowProfilePageData(bool mustUpdateDisplay)
{
    // Ensure this is always up to date
    if(needToUpdateProfileGraphs) {
        SetupColumnAndInjectorAndGasProfileData();
        mustUpdateDisplay = true;

        needToUpdateProfileGraphs = false;
    }
    
    DisplayGasFlowProfilePageGraph();
    
    if(SinglePageGCComponentStatusHasChanged(GAS)) {
        mustUpdateDisplay = true;
    }

    if(mustUpdateDisplay) {

#ifdef WANT_GAS_STATUS_RECTANGLE
        // Reduce display flickering - get the component status from the GC
        // *before* we call GuiLib_Clear()
        if(singleGCComponentPageStatusColorAreas != NULL) {
            UpdateSingleGCComponentPageStatusColorArea(GAS);
        }
#endif
        
#ifndef WANT_GAS_STATUS_RECTANGLE
        // Makes the display flicker - but omitting it means old text is not cleared from the display
#define WANT_GUILIB_CLEAR
#endif

#ifdef WANT_GUILIB_CLEAR
#ifdef USING_BACKGROUND_BITMAP
        DrawBackgroundBitmap(); // 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
#else
        GuiLib_Clear();
#endif
#undef WANT_GUILIB_CLEAR
#endif
        //...except that redrawing the status rectangle effectively clears the text on top of the rectangle -
        // so we do not need GuiLib_Clear here, since all the injector page data appears on top of the rectangle. 
        // Without it, only the text flickers, not the rectangle

#ifdef WANT_GAS_STATUS_RECTANGLE
        // Note - we draw the status rectangle 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
        if(singleGCComponentPageStatusColorAreas != NULL) {
            singleGCComponentPageStatusColorAreas->DisplayGCComponentStatus(GAS);
        }
#endif

#ifdef WANT_COMPONENT_ICON_ON_PROFILE_PAGES
        // And (currently) we draw the component bitmap on top of the rectangle
        if(qspiBitmaps != NULL) {
            qspiBitmaps->DisplayGasComponentBitmap();
        }
#endif // WANT_COMPONENT_ICON_ON_PROFILE_PAGES

        gasFlowProfilePageGraph->Redraw();
    
#ifdef TEST_GUILIB_VLINE_PROFILES
        // Now draw the profile as a solid colour, direct to the display.
        // Use the same parameters as the previous call, in DisplayInjectorTempProfilePageGraph
        gasFlowProfilePageGraphCompleteProfileDataSet->DrawUsingGuiLibVLine();
#endif // TEST_GUILIB_VLINE_PROFILES

        GuiLib_ShowScreen(GuiStruct_GasProfilePage_15, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);
    
        // Repeat the previous call to the version of this function with parameters,
        // made from 'DisplayGasFlowProfilePageGraph()' above (it is more convenient 
        // to calculate the parameter values in 'DisplayGasFlowProfilePageGraph()' than here)
        gasFlowProfilePageGraph->DrawXAxisLabels();
        gasFlowProfilePageGraph->DrawYAxisLabels();
    
        GuiLib_Refresh();    

#define DEBUG_HERE
#ifdef DEBUG_HERE
    char dbg[100];
    sprintf(dbg, "After GuiLib_Clear 99");
    EasyGUIDebugPrint(dbg, 0, 20);
#undef DEBUG_HERE
#endif
    }
}

/*
    Displays the data on the gas calibration page, by copying it to the relevant easyGUI variables,
    and calling the relevant functions to actually display it.
    
    Args: a boolean specifying whether or not the display is actually to be updated.
          Even if it is false when we are called, we may set it true if we discover
          one or more data items has changed - but note that we will not set it false 
          if it is already true. If it is true after we have looked at all 
          the home page data, we will then update the display. (If we are called
          with this value set to false, the caller is effectively saying
          'display the gas page data only if it has changed').
          
    No return code.
*/
void GetGCStatusLoop::DisplayGasCalibrationPageData(bool mustUpdateDisplay)
{
    //EasyGUIDebugPrint("Gas Calibration Page", 100, 100);
    // Gas pressure and control mode
    
    if(SinglePageGCComponentStatusHasChanged(GAS)) {
        mustUpdateDisplay = true;
    }

    if(mustUpdateDisplay) {

        // Reduce display flickering - get the component status from the GC
        // *before* we call GuiLib_Clear()
        if(singleGCComponentPageStatusColorAreas != NULL) {
            UpdateSingleGCComponentPageStatusColorArea(GAS);
        }
        
        // Makes the display flicker - but omitting it means old text is not cleared from the display
//#define WANT_GUILIB_CLEAR
#ifdef WANT_GUILIB_CLEAR
#ifdef USING_BACKGROUND_BITMAP
        DrawBackgroundBitmap(); // 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
#else
        GuiLib_Clear();
#endif
#undef WANT_GUILIB_CLEAR
#endif
        //...except that redrawing the status rectangle effectively clears the text on top of the rectangle -
        // so we do not need GuiLib_Clear here, since all the gas page data appears on top of the rectangle. 
        // Without it, only the text flickers, not the rectangle

        // Note - we draw the status rectangle 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
#ifdef WANT_STATUS_RECTANGLE_ON_GAS_CALIB_PAGES        
        if(singleGCComponentPageStatusColorAreas != NULL) {
            singleGCComponentPageStatusColorAreas->DisplayGCComponentStatus(GAS);
        }
#else // Need to clear old text some other way...
#ifdef USING_BACKGROUND_BITMAP
        DrawBackgroundBitmap();
#else
        GuiLib_Clear();
#endif // USING_BACKGROUND_BITMAP
#endif // WANT_STATUS_RECTANGLE_ON_GAS_CALIB_PAGES

        GuiLib_ShowScreen(GuiStruct_GasCalibrationPage_Def, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);
    
        GuiLib_Refresh();    

#define DEBUG_HERE
#ifdef DEBUG_HERE
    char dbg[100];
    sprintf(dbg, "After GuiLib_Clear 105");
    EasyGUIDebugPrint(dbg, 0, 20);
#undef DEBUG_HERE
#endif
    }
}


/*
    Displays the data on the gas backpressure DAC page, by copying it to the relevant easyGUI variables,
    and calling the relevant functions to actually display it.
    
    Args: a boolean specifying whether or not the display is actually to be updated.
          Even if it is false when we are called, we may set it true if we discover
          one or more data items has changed - but note that we will not set it false 
          if it is already true. If it is true after we have looked at all 
          the home page data, we will then update the display. (If we are called
          with this value set to false, the caller is effectively saying
          'display the gas page data only if it has changed').
          
    No return code.
*/
void GetGCStatusLoop::DisplayGasBackPressureDACPageData(bool mustUpdateDisplay)
{
    //EasyGUIDebugPrint("Gas Backpressure DAC Page", 100, 100);
    // Gas pressure and control mode
    
    if(SinglePageGCComponentStatusHasChanged(GAS)) {
        mustUpdateDisplay = true;
    }

    if(mustUpdateDisplay) {

        // Reduce display flickering - get the component status from the GC
        // *before* we call GuiLib_Clear()
        if(singleGCComponentPageStatusColorAreas != NULL) {
            UpdateSingleGCComponentPageStatusColorArea(GAS);
        }
        
        // Makes the display flicker - but omitting it means old text is not cleared from the display
//#define WANT_GUILIB_CLEAR
#ifdef WANT_GUILIB_CLEAR
#ifdef USING_BACKGROUND_BITMAP
        DrawBackgroundBitmap(); // 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
#else
        GuiLib_Clear();
#endif
#undef WANT_GUILIB_CLEAR
#endif
        //...except that redrawing the status rectangle effectively clears the text on top of the rectangle -
        // so we do not need GuiLib_Clear here, since all the gas page data appears on top of the rectangle. 
        // Without it, only the text flickers, not the rectangle

        // Note - we draw the status rectangle 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
#ifdef WANT_STATUS_RECTANGLE_ON_GAS_CALIB_PAGES
        if(singleGCComponentPageStatusColorAreas != NULL) {
            singleGCComponentPageStatusColorAreas->DisplayGCComponentStatus(GAS);
        }
#else // Need to clear old text some other way...
#ifdef USING_BACKGROUND_BITMAP
        DrawBackgroundBitmap();
#else
        GuiLib_Clear();
#endif // USING_BACKGROUND_BITMAP
#endif // WANT_STATUS_RECTANGLE_ON_GAS_CALIB_PAGES

        GuiLib_ShowScreen(GuiStruct_GasBackPressureDACPage_Def, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);
    
        GuiLib_Refresh();    

#define DEBUG_HERE
#ifdef DEBUG_HERE
    char dbg[100];
    sprintf(dbg, "After GuiLib_Clear 105");
    EasyGUIDebugPrint(dbg, 0, 20);
#undef DEBUG_HERE
#endif
    }
}


/*
    Displays the data on the gas channel DAC and ADC page, by copying it to the relevant easyGUI variables,
    and calling the relevant functions to actually display it.
    
    Args: a boolean specifying whether or not the display is actually to be updated.
          Even if it is false when we are called, we may set it true if we discover
          one or more data items has changed - but note that we will not set it false 
          if it is already true. If it is true after we have looked at all 
          the home page data, we will then update the display. (If we are called
          with this value set to false, the caller is effectively saying
          'display the gas page data only if it has changed').
          
    No return code.
*/
void GetGCStatusLoop::DisplayGasChannelDACAndADCPageData(bool mustUpdateDisplay)
{
    //EasyGUIDebugPrint("Gas Backpressure DAC Page", 100, 100);
    // Gas pressure and control mode
    
    if(SinglePageGCComponentStatusHasChanged(GAS)) {
        mustUpdateDisplay = true;
    }

    if(mustUpdateDisplay) {

        // Reduce display flickering - get the component status from the GC
        // *before* we call GuiLib_Clear()
        if(singleGCComponentPageStatusColorAreas != NULL) {
            UpdateSingleGCComponentPageStatusColorArea(GAS);
        }
        
        // Makes the display flicker - but omitting it means old text is not cleared from the display
//#define WANT_GUILIB_CLEAR
#ifdef WANT_GUILIB_CLEAR
#ifdef USING_BACKGROUND_BITMAP
        DrawBackgroundBitmap(); // 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
#else
        GuiLib_Clear();
#endif
#undef WANT_GUILIB_CLEAR
#endif
        //...except that redrawing the status rectangle effectively clears the text on top of the rectangle -
        // so we do not need GuiLib_Clear here, since all the gas page data appears on top of the rectangle. 
        // Without it, only the text flickers, not the rectangle

        // Note - we draw the status rectangle 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
#ifdef WANT_STATUS_RECTANGLE_ON_GAS_CALIB_PAGES
        if(singleGCComponentPageStatusColorAreas != NULL) {
            singleGCComponentPageStatusColorAreas->DisplayGCComponentStatus(GAS);
        }
#else // Need to clear old text some other way...
#ifdef USING_BACKGROUND_BITMAP
        DrawBackgroundBitmap();
#else
        GuiLib_Clear();
#endif // USING_BACKGROUND_BITMAP
#endif // WANT_STATUS_RECTANGLE_ON_GAS_CALIB_PAGES

        GuiLib_ShowScreen(GuiStruct_GasChannelDACAndADCPage_Def, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);
    
        GuiLib_Refresh();    

#define DEBUG_HERE
#ifdef DEBUG_HERE
    char dbg[100];
    sprintf(dbg, "After GuiLib_Clear 105");
    EasyGUIDebugPrint(dbg, 0, 20);
#undef DEBUG_HERE
#endif
    }
}


/*
    A public function allowing other classes (specifically GasCalibrationPageHandler),
    having changed something on the gas calibration page, to tell us to redisplay it
*/
void GetGCStatusLoop::ForceUpdateOfGasCalibrationPage(void)
{
    if(currentPage == GuiStruct_GasCalibrationPage_Def) {
        DisplayGasCalibrationPageData(true);
    }
}


/*
    A public function allowing other classes (specifically ColumnDHManualCalibrationPageHandler),
    having changed something on the gas calibration page, to tell us to redisplay it
*/
void GetGCStatusLoop::ForceUpdateOfColumnDHManualCalibrationPage(void)
{
    if(currentPage == GuiStruct_ColumnDHManualCalibrationPage_Def) {
        DisplayColumnDHManualCalibrationPageData(true);
    }
}


/*
    A public function allowing other classes (specifically ColumnDHManualCalibrationPageHandler),
    having changed something on the manual calibration page, to tell us to redisplay it
*/
void GetGCStatusLoop::ForceUpdateOfColumnDHSensorCalibrationPage(void)
{
    if(currentPage == GuiStruct_ColumnDHSensorCalibration_Def) {
        DisplayColumnDHSensorCalibrationPageData(true);
    }
}


/*
    A public function allowing other classes (specifically ColumnDHPSUDACPageHandler),
    having changed something on the PSU DAC page, to tell us to redisplay it
*/
void GetGCStatusLoop::ForceUpdateOfColumnDHPSUDACPage(void)
{
    if(currentPage == GuiStruct_PSU_DAC_Page_Def) {
        DisplayColumnDHPSUDACPageData(true);
    }
}


/*
    A public function allowing other classes (specifically ColumnDHAutoCalibrationPageHandler),
    having changed something on the auto calibration page, to tell us to redisplay it
*/
void GetGCStatusLoop::ForceUpdateOfColumnDHAutoCalibrationPage(void)
{
    if(currentPage == GuiStruct_ColumnDHAutoCalibrationPage_Def) {
        DisplayColumnDHAutoCalibrationPageData(true);
    }
}


/*
    A public function allowing other classes (specifically GasBackPressureDACPageHandler),
    having changed something on the gas back pressure DAC page, to tell us to redisplay it
*/
void GetGCStatusLoop::ForceUpdateOfGasBackPressureDACPage(void)
{
    if(currentPage == GuiStruct_GasBackPressureDACPage_Def) {
        DisplayGasBackPressureDACPageData(true);
    }
}


/*
    A public function allowing other classes (specifically GasChannelDACAndADCPageHandler),
    having changed something on the channel DAC and ADC page, to tell us to redisplay it
*/
void GetGCStatusLoop::ForceUpdateOfGasChannelDACAndADCPage(void)
{
    if(currentPage == GuiStruct_GasChannelDACAndADCPage_Def) {
        DisplayGasChannelDACAndADCPageData(true);
    }
}



/*
    Returns the length of time remaining until the column method finishes.
    Value is in minutes.
*/
float GetGCStatusLoop::GetColumnMethodTimeRemaining(void)
{
    float currentColumnMethodRunTime;
    GetRunTime(&currentColumnMethodRunTime);
//    float totalColumnMethodMethodTime = runningColumnPageGraphCompleteProfileDataSet->GetTotalMethodTime();
    float totalColumnMethodMethodTime = runningColumnPageGraphCompleteProfileDataSet->GetNonRoundedTotalMethodTime();
    

    char dbg[100];
    sprintf(dbg, "GCMTR - %f %f", currentColumnMethodRunTime, totalColumnMethodMethodTime);
    EasyGUIDebugPrintWithCounter(dbg, 125, 350);

    // This value will be in minutes 
    return (totalColumnMethodMethodTime - currentColumnMethodRunTime);
}

/*
    If the GC is running, updates the easyGUI variables 
    that display the run time.
    
    Returns true if it updated [any of] the variables, false if not
*/
bool GetGCStatusLoop::UpdateMethodRunTimeEasyGUIVariables(bool runHasCompleted)
{
    bool variableUpdated = false;
    char buff[50];
    
    if(GCIsRunning()) {
        float currentColumnMethodRunTime;
        GetRunTime(&currentColumnMethodRunTime);

        // Always display in units of 0.1 minute
        sprintf(buff, "%.1f", currentColumnMethodRunTime);
       
        if(strcmp(GuiVar_runTimeElapsed, buff) != 0) {
            strcpy(GuiVar_runTimeElapsed, buff);
            variableUpdated = true;
        }
        
        
        float columnMethodTimeRemaining = GetColumnMethodTimeRemaining();
        
        if(columnMethodTimeRemaining >= 0.0f) {
            // We display the value in 'units' of 0.1 minute
            sprintf(buff, "(Time remaining: %.1f minutes)", columnMethodTimeRemaining);
        } else {
            strcpy(buff, "(Time remaining: 0.0 minutes)"); // *Never* display a negative value
        }
       
        if(strcmp(GuiVar_runTimeRemaining, buff) != 0) {
            strcpy(GuiVar_runTimeRemaining, buff);
            variableUpdated = true;
        }
        
    } else if (runHasCompleted) {

        strcpy(buff, "0.0");
       
        if(strcmp(GuiVar_runTimeRemaining, buff) != 0) {
            strcpy(GuiVar_runTimeRemaining, buff);
            variableUpdated = true;
        }
        
        // GuiVar_runTimeElapsed needs to be an empty string
        if(GuiVar_runTimeElapsed[0] != '\0') {
            GuiVar_runTimeElapsed[0] = '\0';
            variableUpdated = true;
        }
    }
    
    return variableUpdated;
}

/*
    Update the easyGUI variable that displays the column status on Column page 1
    and Column DH page 1
*/
void GetGCStatusLoop::UpdateColumnStatusEasyGUIVariable(void)
{
    GetComponentStatusString(COLUMN, GuiVar_columnStatus);
}

void GetGCStatusLoop::UpdateInjectorStatusEasyGUIVariable(void)
{
    GetComponentStatusString(INJECTOR, GuiVar_injectorStatus);
}

/*
    Update the calibrated range and current position of the progress bar
    displayed on 'RunningPage1' - then re-display it.
    
    No arguments, no return code
*/
void GetGCStatusLoop::UpdateAndDisplayRunningPage1ProgressBar(bool runHasCompleted)
{
    if(runningPage1ProgressBar != NULL) {

        float totalColumnMethodMethodTime = runningColumnPageGraphCompleteProfileDataSet->GetNonRoundedTotalMethodTime();
    
        if(GCIsRunning()) {
            float currentColumnMethodRunTime;
            GetRunTime(&currentColumnMethodRunTime);
            //float totalColumnMethodMethodTime = runningColumnPageGraphCompleteProfileDataSet->GetTotalMethodTime();
            runningPage1ProgressBar->SetCalibratedRange((double)totalColumnMethodMethodTime);
            
            runningPage1ProgressBar->UpdateCalibratedPosition((double)currentColumnMethodRunTime, true);
        } else if (runHasCompleted) {
            runningPage1ProgressBar->DisplayBarComplete(true);
        }
    }    
}


void GetGCStatusLoop::SetRunningPage1ProgressBarToZero(void)
{
    if(runningPage1ProgressBar != NULL) {
        runningPage1ProgressBar->UpdateCalibratedPosition(0.0, true);
    }
}

/*
    Displays the data on the 'GC is running' page, by copying it to the relevant easyGUI variables,
    and calling the relevant functions to actually display it.
    
    Args: a boolean specifying whether or not the display is actually to be updated.
          Even if it is false when we are called, we may set it true if we discover
          one or more data items has changed - but note that we will not set it false 
          if it is already true. If it is true after we have looked at all 
          the home page data, we will then update the display. (If we are called
          with this value set to false, the caller is effectively saying
          'display the running page data only if it has changed').
          
    No return code.
*/
void GetGCStatusLoop::DisplayRunningPageData(bool mustUpdateDisplay, bool runHasCompleted)
{
    if(UpdateMethodRunTimeEasyGUIVariables(runHasCompleted)) {
        mustUpdateDisplay = true;
    }
    
    if(mustUpdateDisplay) {

        // Makes the display flicker - but omitting it means old text is not cleared from the display
#ifdef USING_BACKGROUND_BITMAP
        DrawBackgroundBitmap(); 
#else
        GuiLib_Clear();
#endif

        GuiLib_ShowScreen(GuiStruct_RunningPage1_7, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);
    
        UpdateAndDisplayRunningPage1ProgressBar(runHasCompleted);
    
        GuiLib_Refresh();
    }

}


/*
    When the GC starts running a method, set up the easyGUI variables that display
    the column temperature, etc, on the relevant graph/profile pages.
    This is so that they are all set to valid values before they are displayed.
    
    This *must* be called every time the GC starts running, whatever the reason
      
    No arguments, no return value
*/
void GetGCStatusLoop::SetupTemperatureWhileRunningEasyGUIVariables(void)
{
//    Get the column type from the GC, put it in the 'runningColumnType' variable.
//    Provided so that:
//     (1) this is *always* called when we start running
//     (2) we do not have to get the column type every time we read the temperature 
//         (the column type is hardly likely to change while we are running)
    runningColumnType = GetColumnType();
    
    if(runningColumnType == DIRECTLY_HEATED_COLUMN) {
        GetDirectlyHeatedColumnTemperature(GuiVar_columnTemperatureWhileRunning, false);
    } else {
        GetColumnTemperature(GuiVar_columnTemperatureWhileRunning, false);
    }

    GetInjectorTemperature(GuiVar_injectorTemperatureWhileRunning, false);

    GetCurrentGasPressure(GuiVar_gasPressureWhileRunning, false, true);
}


/*
    Displays the data on the 'running column' page.
    
    Args: a boolean specifying whether or not the display is actually to be updated.
          Even if it is false when we are called, we may set it true if we discover
          one or more data items has changed - but note that we will not set it false 
          if it is already true. If it is true after we have looked at all 
          the home page data, we will then update the display. (If we are called
          with this value set to false, the caller is effectively saying
          'display the running settings page data only if it has changed').
          
    No return code.
*/
void GetGCStatusLoop::DisplayRunningColumnPageData(bool mustUpdateDisplay, bool runHasCompleted)
{
    // This page consists principally of an easyGUI graph, to which we assign several datasets:
    //
    // - dataset 0 is a line representing the temperature profile of the run from 'now' to the finish
    // - dataset 1 is a bar chart representing the temperature profile of the run from 'now' to the finish
    // - dataset 2 is a line representing the temperature profile from the start to 'now'
    // - dataset 3 is a bar chart representing the temperature profile of the run from the start to 'now'
    // - dataset 4 is a single dot at the current time and temperature
    
    if(GCIsRunning() || runHasCompleted) {

        // SetupRunningColumnPageGraphPartialDataSetsToMatchCurrentRunTime 
        // returns true if it has updated the datasets, false otherwise
        if(SetupRunningColumnPageGraphPartialDataSetsToMatchCurrentRunTime(runHasCompleted)) {
            mustUpdateDisplay = true;
        }
    }
    
    char buff[40];
    if(runningColumnType == DIRECTLY_HEATED_COLUMN) {
        GetDirectlyHeatedColumnTemperature(buff, false);
    } else {
        GetColumnTemperature(buff, false);
    }
    if(strcmp(buff, GuiVar_columnTemperatureWhileRunning) != 0) {
        strcpy(GuiVar_columnTemperatureWhileRunning, buff);
        mustUpdateDisplay = true;
    }
    
    // Do this *before* updating the datasets, not after - otherwise we don't see the data at all
    if(mustUpdateDisplay) {

#ifdef USING_DATASET_4 // If we are not using dataset4, we do not need to erase the previous 'dots' - 
                       // all elements of the graph will get redrawn anyway, so we do not need to clear it

        // Makes the display flicker - but omitting it means old text is not cleared from the display
#ifdef USING_BACKGROUND_BITMAP
        DrawBackgroundBitmap(); 
#else
        GuiLib_Clear();
#endif
        // But the text does not change - so why do we need this? 
        // Answer - because otherwise the 'dot at current time' (dataset 4) does not get erased when we draw the next one

#else 
        // We are not erasing the background, to minimise flickering of the graph.
        // Therefore, must manually erase background of 'column temperature while running' variable
        GuiLib_FillBox(400, 45, 550, 75, 0xFFFF); // White background
        
#endif // USING_DATASET_4

        GuiLib_ShowScreen(GuiStruct_RunningColumnPage_25, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);
    
        GuiLib_Refresh();
    }

    TimeUnit timeUnit = MINUTES;
    if(runningColumnPageGraphCompleteProfileDataSet->GetTotalMethodTime() < methodTimeUnitsThreshold) {
        timeUnit = SECONDS;
    }


    const float yAxisScaleFactor = 10.0f;
    
    // Dataset 0
    runningColumnPageGraph->SetDataForGraphDataSetInTenthsOfMinutes(0, yAxisScaleFactor, runningColumnPageGraphDataSet0);
    
    // Dataset 1 
    runningColumnPageGraph->SetDataForGraphDataSetInTenthsOfMinutes(1, yAxisScaleFactor, runningColumnPageGraphDataSet1);
    
    // Dataset 2
    runningColumnPageGraph->SetDataForGraphDataSetInTenthsOfMinutes(2, yAxisScaleFactor, runningColumnPageGraphDataSet2);
    
    // Dataset 3
    runningColumnPageGraph->SetDataForGraphDataSetInTenthsOfMinutes(3, yAxisScaleFactor, runningColumnPageGraphDataSet3);
    
#ifdef USING_DATASET_4
    // Dataset 4
    runningColumnPageGraph->SetDataForGraphDataSetInTenthsOfMinutes(4, yAxisScaleFactor, runningColumnPageGraphDataSet4);
#endif // USING_DATASET_4
    
    runningColumnPageGraph->SetXAxisUnits(timeUnit);
    
    
    // The tick sizes must match those set in easyGUI - we do not seem 
    // to be able to get them at runtime
    GuiConst_INT32S minX, maxX;
    //GuiConst_INT32S tickSize = (timeUnit == SECONDS) ? 300 : 100;
    // Always in 1/10 minutes
    GuiConst_INT32S xAxisTickSize = (timeUnit == SECONDS) ? 5 : 100; // 5 == 0.5 minutes (i.e. 30 seconds), 100 == 10 minutes
    runningColumnPageGraphCompleteProfileDataSet->GetXAxisRangeInTenthsOfMinutes(&minX, &maxX, xAxisTickSize );
    runningColumnPageGraph->SetXAxisRange(0, maxX); // Always start X axis at zero
    
    GuiConst_INT32S minY, maxY;
    GuiConst_INT32S yAxisTickSize = 50;
    runningColumnPageGraphCompleteProfileDataSet->GetYAxisRange(&minY, &maxY, yAxisTickSize);
    runningColumnPageGraph->SetYAxisRange(0, (maxY * yAxisScaleFactor)); // Always start Y axis at zero
    
    
    runningColumnPageGraph->DrawAxes();

    // We need to draw the X axis labels ourselves, since our time values are in units of 0.1 minute,
    // but easyGUI graphs work only in integers - we therefore have to multiply the time values by 10
    // before passing them to easyGUI, and if we let easyGUI draw its own values on the X axis, 
    // they would be 10 times the correct values.
    // Graph coordinates copied from easyGUI - I cannot find a way of obtaining them at runtime.
    if(timeUnit == SECONDS) {
        runningColumnPageGraph->DrawXAxisLabels(0, (maxX * 6), (xAxisTickSize * 6), 170, 335, 500);
    } else {
        runningColumnPageGraph->DrawXAxisLabels(0, (maxX / 10), (xAxisTickSize / 10), 170, 335, 500);
    }

    // Similar to the X axis - the values we pass to the easyGUI graph are scaled to be larger
    // than the real values, to get round the fact that easyGUI graphs work in integers.
    // (This can cause the profile to appear to be 'stepped', which obviously we do not want.)
    // We must therefore draw the Y axis values ourselves.
    runningColumnPageGraph->DrawYAxisLabels(0, maxY, yAxisTickSize, 170, 335, 250);

    runningColumnPageGraph->DrawDataSet(0);
    runningColumnPageGraph->ShowDataSet(0);

    runningColumnPageGraph->DrawDataSet(1);
    runningColumnPageGraph->ShowDataSet(1);

    runningColumnPageGraph->DrawDataSet(2);
    runningColumnPageGraph->ShowDataSet(2);

    runningColumnPageGraph->DrawDataSet(3);
    runningColumnPageGraph->ShowDataSet(3);

#ifdef USING_DATASET_4
    runningColumnPageGraph->DrawDataSet(4);
    runningColumnPageGraph->ShowDataSet(4);
#endif // USING_DATASET_4
    
    runningColumnPageGraph->Redraw(); // This only redraws the graph axes, etc - not the data
    
#ifdef TEST_GUILIB_VLINE_PROFILES
    GuiConst_INTCOLOR graphColour1 = SixteenBitColorValue(100, 100, 100);
    GuiConst_INTCOLOR graphColour2 = SixteenBitColorValue(200, 200, 200);
    float runTime;
    GetRunTime(&runTime);
    double colourBoundaryX = (double) runTime;
//    columnTempProfilePageGraphCompleteProfileDataSet->DrawUsingGuiLibVLine(170, 335, ((double) 500 / (double) (maxX / 10)), ((double) -250 / (double) maxY), graphColour1, graphColour2, colourBoundaryX);
// Surely...
    runningColumnPageGraphCompleteProfileDataSet->DrawUsingGuiLibVLine(170, 335, ((double) 500 / (double) (maxX / 10)), ((double) -250 / (double) maxY), graphColour1, graphColour2, colourBoundaryX);
    // &&&&
#endif // TEST_GUILIB_VLINE_PROFILES

    // Does this solve 'pause before graph displayed' problem with double buffering?
    if(mustUpdateDisplay) {
        GuiLib_Refresh();
    }
    // Answer - yes it does - although there is still a (just noticeable) pause
    // (without double buffering, we do not need the above 'if' - 
    // the graph gets redisplayed without it)
}

/*
    Displays the data on the 'running detector' page.
    
    Args: a boolean specifying whether or not the display is actually to be updated.
          Even if it is false when we are called, we may set it true if we discover
          one or more data items has changed - but note that we will not set it false 
          if it is already true. If it is true after we have looked at all 
          the data to be displayed, we will then update the display. (If we are called
          with this value set to false, the caller is effectively saying
          'display the running settings page data only if it has changed').
          
    No return code.
*/
void GetGCStatusLoop::DisplayRunningDetectorPageData(bool mustUpdateDisplay)
{
    char buff[40];
    
    GetDetectorType(buff, false, false);
    if(strcmp(buff, GuiVar_detectorType2) != 0) {
        mustUpdateDisplay = true;
        
        strcpy(GuiVar_detectorType2, buff);
    }
    
//    if(detectorType != FPD_DETECTOR) {
        GetDetectorRange(buff);
        if(strcmp(buff, GuiVar_detectorRange) != 0) {
            mustUpdateDisplay = true;
            
            strcpy(GuiVar_detectorRange, buff);
        }
        // FPD detector has a different GC command for range - see below
//    }
        
    GetFuelFlowRate(buff);    
    if(strcmp(buff, GuiVar_detectorFuelFlowRate) != 0) {
        mustUpdateDisplay = true;
        
        strcpy(GuiVar_detectorFuelFlowRate, buff);
    }
    
    GetDetectorTemperature(buff);
    if(strcmp(buff, GuiVar_detectorTemperature) != 0) {
        strcpy(GuiVar_detectorTemperature, buff);

        mustUpdateDisplay = true;
    }
    
    GetDetectorTargetTemperature(buff, stringFormatdegCUnits);
    if(strcmp(buff, GuiVar_detectorTargetTemperature2) != 0) {
        strcpy(GuiVar_detectorTargetTemperature2, buff);

        mustUpdateDisplay = true;
    }
        
// *** 14 MarWhy is this code commented out?? ***
/*
    if(detectorType == TCD_DETECTOR) {
        
        GetTCDDetectorFilamentTemperature(buff);
        if(strcmp(buff, GuiVar_tcdDetectorFilamentTemperature) != 0) {
            mustUpdateDisplay = true;
            
            strcpy(GuiVar_tcdDetectorFilamentTemperature, buff);
        }
        
        GetTCDDetectorFilamentPolarity(buff);
        if(strcmp(buff, GuiVar_tcdDetectorFilamentPolarity) != 0) {
            mustUpdateDisplay = true;
            
            strcpy(GuiVar_tcdDetectorFilamentPolarity, buff);
        }
        
    } else if(detectorType == ECD_DETECTOR) {
        
        GetECDDetectorCurrent(buff);
        if(strcmp(buff, GuiVar_ecdDetectorCurrent) != 0) {
            mustUpdateDisplay = true;
            
            strcpy(GuiVar_ecdDetectorCurrent, buff);
        }

    } else if(detectorType == FPD_DETECTOR) {
        
        GetFPDDetectorRange(buff);
        if(strcmp(buff, GuiVar_fpdDetectorRange) != 0) {
            mustUpdateDisplay = true;
            
            strcpy(GuiVar_fpdDetectorRange, buff);
        }

        GetFPDDetectorSensitivity(buff);
        if(strcmp(buff, GuiVar_fpdDetectorSensitivity) != 0) {
            mustUpdateDisplay = true;
            
            strcpy(GuiVar_fpdDetectorSensitivity, buff);
        }

    } else {
        
        GetDetectorTemperature(buff);
        if(strcmp(buff, GuiVar_detectorTemperature) != 0) {
            mustUpdateDisplay = true;
            
            strcpy(GuiVar_detectorTemperature, buff);
        }
    }
*/

    if(mustUpdateDisplay) {

        // Makes the display flicker - but omitting it means old text is not cleared from the display
#ifdef USING_BACKGROUND_BITMAP
        DrawBackgroundBitmap(); 
#else
        GuiLib_Clear();
#endif

        GuiLib_ShowScreen(GuiStruct_RunningDetectorPage_27, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);
    
        GuiLib_Refresh();    

    }
}

/*
    Displays the data on the 'running injector' page.
    
    Args: a boolean specifying whether or not the display is actually to be updated.
          Even if it is false when we are called, we may set it true if we discover
          one or more data items has changed - but note that we will not set it false 
          if it is already true. If it is true after we have looked at all 
          the home page data, we will then update the display. (If we are called
          with this value set to false, the caller is effectively saying
          'display the running settings page data only if it has changed').
          
    No return code.
*/
void GetGCStatusLoop::DisplayRunningInjectorPageData(bool mustUpdateDisplay)
{
    char buff[40];
    
    GetInjectorTemperature(buff, false);
    if(strcmp(buff, GuiVar_injectorTemperature) != 0) {
        strcpy(GuiVar_injectorTemperature, buff);
        mustUpdateDisplay = true;
    }

    GetInjectionMode(buff);
    if(strcmp(buff, GuiVar_injectionMode2) != 0) {
        strcpy(GuiVar_injectionMode2, buff);
        mustUpdateDisplay = true;
    }

    GetInjectorType(buff, false);
    if(strcmp(buff, GuiVar_injectorType) != 0) {
        strcpy(GuiVar_injectorType, buff);
        mustUpdateDisplay = true;
    }

    GetComponentStatusString(INJECTOR, buff);
    if(strcmp(buff, GuiVar_injectorStatus) != 0) {
        strcpy(GuiVar_injectorStatus, buff);
        mustUpdateDisplay = true;
    }
    
    GetInjectorSplitTime(buff);
    if(strcmp(buff, GuiVar_injectorSplitTime) != 0) {
        strcpy(GuiVar_injectorSplitTime, buff);
        mustUpdateDisplay = true;
    }
    
    GetSplitFlow(buff);
    if(strcmp(buff, GuiVar_injectorSplitFlowRate) != 0) {
        strcpy(GuiVar_injectorSplitFlowRate, buff);
        mustUpdateDisplay = true;
    }

    GetSplitRatio(buff);
    if(strcmp(buff, GuiVar_injectorSplitRatio) != 0) {
        strcpy(GuiVar_injectorSplitRatio, buff);
        mustUpdateDisplay = true;
    }
    
    if(mustUpdateDisplay) {

        // Makes the display flicker - but omitting it means old text is not cleared from the display
#ifdef USING_BACKGROUND_BITMAP
        DrawBackgroundBitmap(); 
#else
        GuiLib_Clear();
#endif

        GuiLib_ShowScreen(GuiStruct_RunningInjectorPage_26, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);
    
        GuiLib_Refresh();    

    }
}

/*
    Displays the data on the 'running gas' page.
    
    Args: a boolean specifying whether or not the display is actually to be updated.
          Even if it is false when we are called, we may set it true if we discover
          one or more data items has changed - but note that we will not set it false 
          if it is already true. If it is true after we have looked at all 
          the home page data, we will then update the display. (If we are called
          with this value set to false, the caller is effectively saying
          'display the running settings page data only if it has changed').
          
    No return code.
*/
void GetGCStatusLoop::DisplayRunningGasPageData(bool mustUpdateDisplay, bool runHasCompleted)
{
    // This page consists principally of an easyGUI graph, to which we assign several datasets:
    //
    // - dataset 0 is a line representing the flow profile of the run from 'now' to the finish
    // - dataset 1 is a bar chart representing the flow profile of the run from 'now' to the finish
    // - dataset 2 is a line representing the flow profile from the start to 'now'
    // - dataset 3 is a bar chart representing the flow profile from the start to 'now'
    // - dataset 4 is a single dot at the current time and flow rate
    
    if(GCIsRunning() || runHasCompleted) {

        // SetupRunningGasPageGraphPartialDataSetsToMatchCurrentRunTime 
        // returns true if it has updated the datasets, false otherwise
        if(SetupRunningGasPageGraphPartialDataSetsToMatchCurrentRunTime(runHasCompleted)) {
            mustUpdateDisplay = true;
        }
    }
    
    char buff[40];
    GetCurrentGasPressure(buff, false, true);
    if(strcmp(buff, GuiVar_gasPressureWhileRunning) != 0) {
        strcpy(GuiVar_gasPressureWhileRunning, buff);
        mustUpdateDisplay = true;
    }

    // Do this *before* updating the datasets, not after - otherwise we don't see the data at all
    if(mustUpdateDisplay) {

#ifdef USING_DATASET_4 
        // Makes the display flicker - but omitting it means old text is not cleared from the display
#ifdef USING_BACKGROUND_BITMAP
        DrawBackgroundBitmap(); 
#else
        GuiLib_Clear();
#endif
        // But the text does not change - so why do we need this? 
        // Answer - because otherwise the 'dot at current time' (dataset 4) does not get erased when we draw the next one
#else 
        // We are not erasing the background, to minimise flickering of the graph.
        // Therefore, must manually erase background of 'gas pressure while running' variable
        GuiLib_FillBox(400, 45, 550, 75, 0xFFFF); // White background
#endif // USING_DATASET_4 - if we are not displaying dataset 4, we do not need to erase the graph - all its other datapoints will get overwritten anyway

        GuiLib_ShowScreen(GuiStruct_RunningGasPage_28, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);
    
        GuiLib_Refresh();
    }

    TimeUnit timeUnit = MINUTES;
    if(runningGasPageGraphCompleteProfileDataSet->GetTotalMethodTime() < methodTimeUnitsThreshold) {
        timeUnit = SECONDS;
    }
    

    const float yAxisScaleFactor = 10.0f;
    
    // Dataset 0
    runningGasPageGraph->SetDataForGraphDataSetInTenthsOfMinutes(0, yAxisScaleFactor, runningGasPageGraphDataSet0);
    
    // Dataset 1 
    runningGasPageGraph->SetDataForGraphDataSetInTenthsOfMinutes(1, yAxisScaleFactor, runningGasPageGraphDataSet1);
    
    // Dataset 2
    runningGasPageGraph->SetDataForGraphDataSetInTenthsOfMinutes(2, yAxisScaleFactor, runningGasPageGraphDataSet2);
    
    // Dataset 3
    runningGasPageGraph->SetDataForGraphDataSetInTenthsOfMinutes(3, yAxisScaleFactor, runningGasPageGraphDataSet3);
    
#ifdef USING_DATASET_4
    // Dataset 4
    runningGasPageGraph->SetDataForGraphDataSetInTenthsOfMinutes(4, yAxisScaleFactor, runningGasPageGraphDataSet4);
#endif // USING_DATASET_4    
    
    runningGasPageGraph->SetXAxisUnits(timeUnit);


    // The tick sizes must match those set in easyGUI - we do not seem 
    // to be able to get them at runtime
    GuiConst_INT32S minX, maxX;
    //GuiConst_INT32S tickSize = (timeUnit == SECONDS) ? 300 : 100;
    // Always in 1/10 minutes
    GuiConst_INT32S xAxisTickSize = (timeUnit == SECONDS) ? 5 : 100; // 5 == 0.5 minutes (i.e. 30 seconds), 100 == 10 minutes
    runningGasPageGraphCompleteProfileDataSet->GetXAxisRangeInTenthsOfMinutes(&minX, &maxX, xAxisTickSize );
    runningGasPageGraph->SetXAxisRange(0, maxX); // Always start X axis at zero
    
    GuiConst_INT32S minY, maxY;
    GuiConst_INT32S yAxisTickSize = 10;
    runningGasPageGraphCompleteProfileDataSet->GetYAxisRange(&minY, &maxY, yAxisTickSize);
    runningGasPageGraph->SetYAxisRange(0, (maxY * yAxisScaleFactor)); // Always start Y axis at zero
    

    runningGasPageGraph->DrawAxes();

    // We need to draw the X axis labels ourselves, since our time values are in units of 0.1 minute,
    // but easyGUI graphs work only in integers - we therefore have to multiply the time values by 10
    // before passing them to easyGUI, and if we let easyGUI draw its own values on the X axis, 
    // they would be 10 times the correct values.
    // Graph coordinates copied from easyGUI - I cannot find a way of obtaining them at runtime.
    if(timeUnit == SECONDS) {
        runningGasPageGraph->DrawXAxisLabels(0, (maxX * 6), (xAxisTickSize * 6), 160, 335, 500);
    } else {
        runningGasPageGraph->DrawXAxisLabels(0, (maxX / 10), (xAxisTickSize / 10), 160, 335, 500);
    }

    // Similar to the X axis - the values we pass to the easyGUI graph are scaled to be larger
    // than the real values, to get round the fact that easyGUI graphs work in integers.
    // (This can cause the profile to appear to be 'stepped', which obviously we do not want.)
    // We must therefore draw the Y axis values ourselves.
    runningGasPageGraph->DrawYAxisLabels(0, maxY, yAxisTickSize, 160, 335, 250);

    runningGasPageGraph->DrawDataSet(0);
    runningGasPageGraph->ShowDataSet(0);

    runningGasPageGraph->DrawDataSet(1);
    runningGasPageGraph->ShowDataSet(1);

    runningGasPageGraph->DrawDataSet(2);
    runningGasPageGraph->ShowDataSet(2);

    runningGasPageGraph->DrawDataSet(3);
    runningGasPageGraph->ShowDataSet(3);

#ifdef USING_DATASET_4
    runningGasPageGraph->DrawDataSet(4);
    runningGasPageGraph->ShowDataSet(4);
#endif // USING_DATASET_4

    runningGasPageGraph->Redraw(); // this only redraws the axes,etc - not the graph data
    
    // Does this solve 'pause before graph displayed' problem with double buffering?
    if(mustUpdateDisplay) {
        GuiLib_Refresh();
    }
    // Answer - yes it does - although there is still a (just noticeable) pause
    // (without double buffering, we do not need the above 'if' - 
    // the graph gets redisplayed without it)
}

/*
    Displays the data on the 'running gas' page.
    
    Args: a boolean specifying whether or not the display is actually to be updated.
          Even if it is false when we are called, we may set it true if we discover
          one or more data items has changed - but note that we will not set it false 
          if it is already true. If it is true after we have looked at all 
          the home page data, we will then update the display. (If we are called
          with this value set to false, the caller is effectively saying
          'display the running settings page data only if it has changed').
          
    No return code.
*/
void GetGCStatusLoop::DisplayRunningInjectorProfilePageData(bool mustUpdateDisplay, bool runHasCompleted)
{
    // This page consists principally of an easyGUI graph, to which we assign several datasets:
    //
    // - dataset 0 is a line representing the injector temperature profile of the run from 'now' to the finish
    // - dataset 1 is a bar chart representing the injector temperature profile of the run from 'now' to the finish
    // - dataset 2 is a line representing the injector temperature profile from the start to 'now'
    // - dataset 3 is a bar chart representing the injector temperature profile from the start to 'now'
    // - dataset 4 is a single dot at the current time and injector temperature 
    
    if(GCIsRunning() || runHasCompleted) {

        // SetupRunningInjectorPageGraphPartialDataSetsToMatchCurrentRunTime 
        // returns true if it has updated the datasets, false otherwise
        if(SetupRunningInjectorPageGraphPartialDataSetsToMatchCurrentRunTime(runHasCompleted)) {
            mustUpdateDisplay = true;
        }
    }
    
    char buff[40];
    GetInjectorTemperature(buff, false);
    if(strcmp(buff, GuiVar_injectorTemperatureWhileRunning) != 0) {
        strcpy(GuiVar_injectorTemperatureWhileRunning, buff);
        mustUpdateDisplay = true;
    }

    // Do this *before* updating the datasets, not after - otherwise we don't see the data at all
    if(mustUpdateDisplay) {

#ifdef USING_DATASET_4 
        // Makes the display flicker - but omitting it means old text is not cleared from the display
#ifdef USING_BACKGROUND_BITMAP
        DrawBackgroundBitmap(); 
#else
        GuiLib_Clear();
#endif
        // But the text does not change - so why do we need this? 
        // Answer - because otherwise the 'dot at current time' (dataset 4) does not get erased when we draw the next one
#else 
        // We are not erasing the background, to minimise flickering of the graph.
        // Therefore, must manually erase background of 'injector temperature while running' variable
        GuiLib_FillBox(400, 45, 550, 75, 0xFFFF); // White background
#endif // USING_DATASET_4 - if we are not displaying dataset 4, we do not need to erase the graph - all its other datapoints will get overwritten anyway

        GuiLib_ShowScreen(GuiStruct_RunningInjectorProfilePage_Def, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);
    
        GuiLib_Refresh();
    }

    TimeUnit timeUnit = MINUTES;
    if(runningInjectorPageGraphCompleteProfileDataSet->GetTotalMethodTime() < methodTimeUnitsThreshold) {
        timeUnit = SECONDS;
    }
    

    const float yAxisScaleFactor = 10.0f;
    
    // Dataset 0
    runningInjectorPageGraph->SetDataForGraphDataSetInTenthsOfMinutes(0, yAxisScaleFactor, runningInjectorPageGraphDataSet0);
    
    // Dataset 1 
    runningInjectorPageGraph->SetDataForGraphDataSetInTenthsOfMinutes(1, yAxisScaleFactor, runningInjectorPageGraphDataSet1);
    
    // Dataset 2
    runningInjectorPageGraph->SetDataForGraphDataSetInTenthsOfMinutes(2, yAxisScaleFactor, runningInjectorPageGraphDataSet2);
    
    // Dataset 3
    runningInjectorPageGraph->SetDataForGraphDataSetInTenthsOfMinutes(3, yAxisScaleFactor, runningInjectorPageGraphDataSet3);
    
#ifdef USING_DATASET_4
    // Dataset 4
    runningInjectorPageGraph->SetDataForGraphDataSetInTenthsOfMinutes(4, yAxisScaleFactor, runningInjectorPageGraphDataSet4);
#endif // USING_DATASET_4    
    
    runningInjectorPageGraph->SetXAxisUnits(timeUnit);


    // The tick sizes must match those set in easyGUI - we do not seem 
    // to be able to get them at runtime
    GuiConst_INT32S minX, maxX;
    //GuiConst_INT32S tickSize = (timeUnit == SECONDS) ? 300 : 100;
    // Always in 1/10 minutes
    GuiConst_INT32S xAxisTickSize = (timeUnit == SECONDS) ? 5 : 100; // 5 == 0.5 minutes (i.e. 30 seconds), 100 == 10 minutes
    runningInjectorPageGraphCompleteProfileDataSet->GetXAxisRangeInTenthsOfMinutes(&minX, &maxX, xAxisTickSize );
    runningInjectorPageGraph->SetXAxisRange(0, maxX); // Always start X axis at zero
    
    GuiConst_INT32S minY, maxY;
    GuiConst_INT32S yAxisTickSize = 50;
    runningInjectorPageGraphCompleteProfileDataSet->GetYAxisRange(&minY, &maxY, yAxisTickSize);
    runningInjectorPageGraph->SetYAxisRange(0, (maxY * yAxisScaleFactor)); // Always start Y axis at zero
    

    runningInjectorPageGraph->DrawAxes();

    // We need to draw the X axis labels ourselves, since our time values are in units of 0.1 minute,
    // but easyGUI graphs work only in integers - we therefore have to multiply the time values by 10
    // before passing them to easyGUI, and if we let easyGUI draw its own values on the X axis, 
    // they would be 10 times the correct values.
    // Graph coordinates copied from easyGUI - I cannot find a way of obtaining them at runtime.
    if(timeUnit == SECONDS) {
        runningInjectorPageGraph->DrawXAxisLabels(0, (maxX * 6), (xAxisTickSize * 6), 160, 335, 500);
    } else {
        runningInjectorPageGraph->DrawXAxisLabels(0, (maxX / 10), (xAxisTickSize / 10), 160, 335, 500);
    }

    // Similar to the X axis - the values we pass to the easyGUI graph are scaled to be larger
    // than the real values, to get round the fact that easyGUI graphs work in integers.
    // (This can cause the profile to appear to be 'stepped', which obviously we do not want.)
    // We must therefore draw the Y axis values ourselves.
    runningInjectorPageGraph->DrawYAxisLabels(0, maxY, yAxisTickSize, 160, 335, 250);

    runningInjectorPageGraph->DrawDataSet(0);
    runningInjectorPageGraph->ShowDataSet(0);

    runningInjectorPageGraph->DrawDataSet(1);
    runningInjectorPageGraph->ShowDataSet(1);

    runningInjectorPageGraph->DrawDataSet(2);
    runningInjectorPageGraph->ShowDataSet(2);

    runningInjectorPageGraph->DrawDataSet(3);
    runningInjectorPageGraph->ShowDataSet(3);

#ifdef USING_DATASET_4
    runningInjectorPageGraph->DrawDataSet(4);
    runningInjectorPageGraph->ShowDataSet(4);
#endif // USING_DATASET_4

    runningInjectorPageGraph->Redraw(); // this only redraws the axes,etc - not the graph data
    
#ifdef TEST_GUILIB_VLINE_PROFILES
    GuiConst_INTCOLOR graphColour1 = SixteenBitColorValue(100, 100, 100);
    GuiConst_INTCOLOR graphColour2 = SixteenBitColorValue(200, 200, 200);
    float runTime;
    GetRunTime(&runTime);
    double colourBoundaryX = (double) runTime;
    runningInjectorPageGraphCompleteProfileDataSet->DrawUsingGuiLibVLine(170, 335, ((double) 500 / (double) (maxX / 10)), ((double) -250 / (double) maxY), graphColour1, graphColour2, colourBoundaryX);
    // &&&&
#endif // TEST_GUILIB_VLINE_PROFILES

    // Does this solve 'pause before graph displayed' problem with double buffering?
    if(mustUpdateDisplay) {
        GuiLib_Refresh();
    }
    // Answer - yes it does - although there is still a (just noticeable) pause
    // (without double buffering, we do not need the above 'if' - 
    // the graph gets redisplayed without it)
}


/*
    Gets the GC embedded software version, and returns it as a null-terminated string, with a descriptive prefix.
    
    Note also that this code is intended to be as efficient as possible.
    
    Args: pointer to a buffer to contain the version, as a null-terminated string
          
    No return code.
*/
void GetGCStatusLoop::GetGCSoftwareVersion(char *version)
{
    char response[50];
    SetGCDeviceReport("QWHO", response);

    // We expect a response like this: "DWHO0320" -> version 3.20
    version[0]  = 'G';
    version[1]  = 'C';
    version[2]  = ' ';
    version[3]  = 's';
    version[4]  = '/';
    version[5]  = 'w';
    version[6]  = ' ';
    version[7]  = 'v';
    version[8]  = 'e';
    version[9]  = 'r';
    version[10] = 's';
    version[11] = 'i';
    version[12] = 'o';
    version[13] = 'n';
    version[14] = ':';
    version[15] = ' ';
    version[16] = response[4];
    version[17] = response[5];
    version[18] = '.';
    version[19] = response[6];
    version[20] = response[7];
    version[21] = '\0';
}

/*
    Gets the actuator software version, and returns it as a null-terminated string, with a descriptive prefix.
    
    Args: pointer to a buffer to contain the version, as a null-terminated string
          
    No return code.
*/
void GetGCStatusLoop::GetActuatorSoftwareVersion(char *version)
{
    char response[50];
    SetGCDeviceReport("QACT0004", response);
    
    // We expect a response like this: "DACnnnnn" (note 5 digits).
    // The value contains 14 bits, and the least significant byte is the software version
    int value;
    sscanf(&response[3], "%d", &value);

    sprintf(version, "Actuator s/w version: %d", (value & 0xFF));
}


/*
    Gets the GC run time, and returns it as a floating point value.
    Note that the GC returns the run time as a four digit value, 
    scaled in units of 0.1 min. This function applies that scaling 
    to the value it returns - i.e. if the GC returns "1234",
    this function returns 123.4 as the float value.

    Remember that the GC sets its runtime to zero at the start of every run.
    
    Args: pointer to a 'float' variable to contain the run time
          
    No return code.
*/
void GetGCStatusLoop::GetRunTime(float *time)
{
    char response[50];
    SetGCDeviceReport("QTIM", response);

    if(response[0] == 'E') {
        // Got "EPKT" response
        *time = -1.0f;
    
        return;
    }
    
    // Must have received a valid response from the GC. We expect a response like this: 
    // "DTIM1234", with run time in units of 0.1 min
    char buff[50];
    buff[0] = response[4];
    buff[1] = response[5];
    buff[2] = response[6];
    buff[3] = '.';
    buff[4] = response[7];
    buff[5] = '\0';
    
    sscanf(buff, "%f", time);
}


/*
    Gets the GC run time, and returns it as a null-terminated string, with a descriptive prefix.
    (The GC sets this to zero at the start of every run.)
    
    Note also that this code is intended to be as efficient as possible.
    
    Args: pointer to a buffer to contain the run time, as a null-terminated string
          
    No return code.
*/
void GetGCStatusLoop::GetRunTime(char *time)
{
    char response[50];
    SetGCDeviceReport("QTIM", response);

    // We expect a response like this: "DTIM1234", with run time in units of 0.1 min
    int index = 0;
    time[index++]  = 'R';
    time[index++]  = 'u';
    time[index++]  = 'n';
    time[index++]  = ' ';
    time[index++]  = 't';
    time[index++]  = 'i';
    time[index++]  = 'm';
    time[index++]  = 'e';
    time[index++]  = ':';
    time[index++]  = ' ';
    
    bool wantNextChars = false;
    if(response[4] != '0') {
        time[index++] = response[4];
        wantNextChars = true;
    }
    if(wantNextChars || (response[5] != '0')) {
        time[index++] = response[5];
    }
    // If the value is zero, make sure we return "0.0" - 
    // we just don't want any zeroes before that
    time[index++] = response[6];
    time[index++] = '.';
    time[index++] = response[7];

    time[index++] = ' ';
    time[index++] = 'm';
    time[index++] = 'i';
    time[index++] = 'n';
    time[index++] = '\0';
}

/*
    Gets the GC serial number (which consists of eight digits), and returns it as a null-terminated string.
    
    Note also that this code is intended to be as efficient as possible.
    
    Args: pointer to a buffer to contain the run time, as a null-terminated string.
          This must be at least twelve bytes long, to allow for a possible error message.
          
    No return code.
*/
void GetGCStatusLoop::GetSerialNumber(char *serialNumber)
{
    char response1[50];
    SetGCDeviceReport("GSN1", response1);
    // We expect a response like this: "DSN1nnnn", where 'nnnn' is the first four digits of the serial number

    char response2[50];
    SetGCDeviceReport("GSN2", response2);
    // We expect a response like this: "DSN2nnnn", where 'nnnn' is the second four digits of the serial number

    int index = 0;
    // But check for "EPKT" first
    if((response1[0] == 'E') || (response2[0] == 'E')) {
        serialNumber[index++] = '*';
        serialNumber[index++] = '*';
        serialNumber[index++] = ' ';
        serialNumber[index++] = 'E';
        serialNumber[index++] = 'r';
        serialNumber[index++] = 'r';
        serialNumber[index++] = 'o';
        serialNumber[index++] = 'r';
        serialNumber[index++] = ' ';
        serialNumber[index++] = '*';
        serialNumber[index++] = '*';
    } else {
        serialNumber[index++] = response1[4];
        serialNumber[index++] = response1[5];
        serialNumber[index++] = response1[6];
        serialNumber[index++] = response1[7];
        serialNumber[index++] = response2[4];
        serialNumber[index++] = response2[5];
        serialNumber[index++] = response2[6];
        serialNumber[index++] = response2[7];
    }
    
    serialNumber[index] = '\0';
}


/*
    Gets the time represented by the GC's real time clock [RTC], and returns it as a null-terminated string.
    
    Args: pointer to a buffer to contain the time, as a null-terminated string.
          This must be at least twenty-five bytes long, to accomodate the string 
          returned by the 'ctime' function
          
    No return code.
*/
void GetGCStatusLoop::GetGCRealTimeClockTime(char *clockTime)
{
    time_t gcRtcValue;
    
    GCRealTimeClock::GetGCClockTime(usbDevice, usbHostGC, &gcRtcValue);
    
    strcpy(clockTime, ctime(&gcRtcValue));
    
    // Remove the newline character from the string returned by 'ctime'
    // (easyGUI displays it as a black rectangle)
    char *cp = clockTime;
    while(*cp) {
        if(*cp == '\n') {
            *cp = '\0';
            break;
        }
        ++cp;
    }
}

/*
    Displays the data on the settings page, by copying it to the relevant easyGUI variables,
    and calling the relevant functions to actually display it.
    
    Args: a boolean specifying whether or not the display is actually to be updated.
          Even if it is false when we are called, we may set it true if we discover
          one or more data items has changed - but note that we will not set it false 
          if it is already true. If it is true after we have looked at all 
          the home page data, we will then update the display. (If we are called
          with this value set to false, the caller is effectively saying
          'display the settings page data only if it has changed').
          
    No return code.
*/
void GetGCStatusLoop::DisplaySettingsPageData(bool mustUpdateDisplay)
{
    //EasyGUIDebugPrint("Settings Page", 100, 100);
    // Various settings
    char buff[60];

    GetGCSoftwareVersion(GuiVar_gcSoftwareVersion);
    // Assume software version cannot change while we are running
    
    GetActuatorSoftwareVersion(GuiVar_actuatorSoftwareVersion);
    
    GetGasControlMode(buff, true, true);
    if(strcmp(buff, GuiVar_gasControlMode) != 0) {
        mustUpdateDisplay = true;
        
        strcpy(GuiVar_gasControlMode, buff);
    }
    
    GetDetectorType(buff, true, true);
    if(strcmp(buff, GuiVar_detectorType) != 0) {
        mustUpdateDisplay = true;
        
        strcpy(GuiVar_detectorType, buff);
    }
    
    GetColumnMaxTemperature(buff, true, true);
    if(strcmp(buff, GuiVar_columnMaxTemp) != 0) {
        mustUpdateDisplay = true;
        
        strcpy(GuiVar_columnMaxTemp, buff);
    }
    
//    GetInjectionMode(buff, true);
    GetInjectorType(buff, true);
    if(strcmp(buff, GuiVar_injectionMode) != 0) {
        mustUpdateDisplay = true;
        
        strcpy(GuiVar_injectionMode, buff);
    }
    
    GetRunTime(buff);
    if(strcmp(buff, GuiVar_runTime) != 0) {
        mustUpdateDisplay = true;
        
        strcpy(GuiVar_runTime, buff);
    }

    GetSerialNumber(buff);
    if(strcmp(buff, GuiVar_gcSerialNumber) != 0) {
        mustUpdateDisplay = true;
        
        strcpy(GuiVar_gcSerialNumber, buff);
    }
    
    GetGCRealTimeClockTime(buff);
    if(strcmp(buff, GuiVar_gcTime) != 0) {
        mustUpdateDisplay = true;
        
        strcpy(GuiVar_gcTime, buff);
    }
    
    if(mustUpdateDisplay) {

        // Makes the display flicker - but omitting it means old text is not cleared from the display
#ifdef USING_BACKGROUND_BITMAP
        DrawBackgroundBitmap(); 
#else
        GuiLib_Clear();
#endif

        GuiLib_ShowScreen(GuiStruct_SettingsPage_5, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);
    
        GuiLib_Refresh();    

#define DEBUG_HERE
#ifdef DEBUG_HERE
    char dbg[100];
    sprintf(dbg, "After GuiLib_Clear 6");
    EasyGUIDebugPrint(dbg, 0, 20);
#undef DEBUG_HERE
#endif
    }
}


/*
    Displays the 'establishing ethernet connection' page - 
    so the user knows we are doing something, and have not locked up.
    
    No args, no return code.
*/
void GetGCStatusLoop::DisplayEthernetConnectionPage(void)
{
#ifdef USING_BACKGROUND_BITMAP
    DrawBackgroundBitmapWithLogo(); // Only on this page and the "Connecting to GC" page
#else
    GuiLib_Clear();
#endif

    GuiLib_ShowScreen(GuiStruct_EthernetConnectionPage_Def, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);

    GuiLib_Refresh();    
}

/*
    Displays the 'failed to load bitmaps' page
    
    No args, no return code.
*/
void GetGCStatusLoop::DisplayFailedToFindBitmapsPage(void)
{
#ifdef USING_BACKGROUND_BITMAP
    DrawBackgroundBitmap(); // Only on this page and the "Connecting to GC" page
#else
    GuiLib_Clear();
#endif

    GuiLib_ShowScreen(GuiStruct_FailedToFindBitmapsPage_0, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);

    GuiLib_Refresh();    
}

/*
    Display the 'Servicing Required' page, telling the user which components
    now need servicing
*/
void GetGCStatusLoop::DisplayServicingRequiredPage(void)
{
    GetSerialNumber(GuiVar_columnSerialNumber);

    char *easyGuiComponentVariables[6] = { GuiVar_componentNeedsServicing1,
                                           GuiVar_componentNeedsServicing2,
                                           GuiVar_componentNeedsServicing3,
                                           GuiVar_componentNeedsServicing4,
                                           GuiVar_componentNeedsServicing5,
                                           GuiVar_componentNeedsServicing6
                                         };
    
    // Make sure we clear the 'component needs servicing' easy GUI variables
    // before we start - we only want to display the components that 
    // *now* need servicing, not whatever needed servicing last time
    int componentIndex = 0;
    while(componentIndex < 6) {
        easyGuiComponentVariables[componentIndex][0] = '\0';
        ++componentIndex;
    }

    // Now find out which components need servicing, and display their names
    componentIndex = 0;
    ServiceInterval* expiredServiceInterval = ServiceInterval::GetNextExpiredServiceInterval(NULL);
    while((expiredServiceInterval != NULL) && (componentIndex < 6)) {
        if(expiredServiceInterval->GetDescriptionLength() < 40) {
            expiredServiceInterval->GetDescription(easyGuiComponentVariables[componentIndex]);
        }
        
        ++componentIndex;
        
        expiredServiceInterval = ServiceInterval::GetNextExpiredServiceInterval(expiredServiceInterval);
    }
    
#ifdef USING_BACKGROUND_BITMAP
    DrawBackgroundBitmap(); 
#else
    GuiLib_Clear();
#endif

    GuiLib_ShowScreen(GuiStruct_ServicingRequired_Def, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);

    GuiLib_Refresh();    
    
    SetCurrentPage(GuiStruct_ServicingRequired_Def);
}


/*
    This needs to be called from SetupAllEasyGUIVariables,
    as well as fromDisplayServicingPage
*/
void GetGCStatusLoop::SetupServicingPageEasyGUIVariables(void)
{
    ServiceInterval* serviceInterval;
    
    serviceInterval = ServiceInterval::GetServiceInterval(0);
    if(serviceInterval != NULL) {
        serviceInterval->GetDescription(GuiVar_componentSetupServicing1);
    }
    
    serviceInterval = ServiceInterval::GetServiceInterval(1);
    if(serviceInterval != NULL) {
        serviceInterval->GetDescription(GuiVar_componentSetupServicing2);
    }
    
    serviceInterval = ServiceInterval::GetServiceInterval(2);
    if(serviceInterval != NULL) {
        serviceInterval->GetDescription(GuiVar_componentSetupServicing3);
    }
    
    serviceInterval = ServiceInterval::GetServiceInterval(3);
    if(serviceInterval != NULL) {
        serviceInterval->GetDescription(GuiVar_componentSetupServicing4);
    }
    
    serviceInterval = ServiceInterval::GetServiceInterval(4);
    if(serviceInterval != NULL) {
        serviceInterval->GetDescription(GuiVar_componentSetupServicing5);
    }
    
    serviceInterval = ServiceInterval::GetServiceInterval(5);
    if(serviceInterval != NULL) {
        serviceInterval->GetDescription(GuiVar_componentSetupServicing6);
    }
}

/*
    Display the 'Servicing' page, allowing our engineer to setup 
    the service intervals for each [serviceable] component
*/
void GetGCStatusLoop::DisplayServicingPage(void)
{
    SetupServicingPageEasyGUIVariables();
    
#ifdef USING_BACKGROUND_BITMAP
    DrawBackgroundBitmap(); 
#else
    GuiLib_Clear();
#endif

    GuiLib_ShowScreen(GuiStruct_ServicingPage_Def, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);

    GuiLib_Refresh();    
    
    SetCurrentPage(GuiStruct_ServicingPage_Def);
}


/*
    Displays the 'downloading method' page - to try and speed this operation up, 
    we do not allow the user to do anything else while we do this -
    displaying this page makes that clear to the user
    
    After completing the download, call 'UndisplayDownloadingMethodPage'
    - currently, this always returns to the Running page if the GC is running,
     or the Home page if it is not
    
    No args, no return code.
*/
void GetGCStatusLoop::DisplayDownloadingMethodPage(void)
{
#ifdef USING_BACKGROUND_BITMAP
    DrawBackgroundBitmap(); 
#else
    GuiLib_Clear();
#endif

    GuiLib_ShowScreen(GuiStruct_DownloadingMethodPage_Def, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);

    GuiLib_Refresh();   
    
    currentPage = GuiStruct_DownloadingMethodPage_Def;
}

/*
    Restores our display after the method has downloaded - 
    currently, we always return to the Home page unless the GC is running,
    in which case we return to the main Running page
    
    No args, no return code
*/
void GetGCStatusLoop::UndisplayDownloadingMethodPage(void)
{
#ifdef USING_BACKGROUND_BITMAP
    DrawBackgroundBitmap(); 
#else
    GuiLib_Clear();
#endif

    int gcStatus = GetGCStatus();
    if(GCStateOrFaultCode::GetSimplifiedGCState(gcStatus) == GC_RUNNING) {
        currentPage = GuiStruct_RunningPage1_7; 
    } else {
        currentPage = GuiStruct_HomePage_1; 
    }
    
    DisplayCurrentPageData(true);
}


/*
    Displays the data for the current page (easyGUI "structure"), by copying it 
    to the relevant easyGUI variables, and calling the relevant functions to actually display it.
    
    Args: a boolean specifying whether or not the display is actually to be updated.
          Even if it is false when we are called, we may set it true if we discover
          one or more data items has changed - but note that we will not set it false 
          if it is already true. If it is true after we have looked at all 
          the home page data, we will then update the display. (If we are called
          with this value set to false, the caller is effectively saying
          'display the current page data only if it has changed').
          
    No return code.
*/
void GetGCStatusLoop::DisplayCurrentPageData(bool mustUpdateDisplay)
{
    // We don't do re-entrancy here - can get random crashes 
    // if the user switches between pages too quickly
    if(displayingData) {
        return;
    }
    
    displayingData = true;
    
    UpdateAllGCComponentStatusColorAreas();
    
    switch(currentPage) {
        case GuiStruct_HomePage_1:
            DisplayHomePageData(mustUpdateDisplay);
            break;
        case GuiStruct_ColumnPage1_2:
            DisplayColumnPageData(mustUpdateDisplay, CONVENTIONAL_COLUMN, GuiStruct_ColumnPage1_2);
            break;
        case GuiStruct_ColumnPage2_9:
            DisplayColumnInformationPageData(mustUpdateDisplay, CONVENTIONAL_COLUMN, GuiStruct_ColumnPage2_9);
            break;
        case GuiStruct_ColumnMethodPage_Def:
            DisplayColumnMethodPageData(mustUpdateDisplay);
            break;
        case GuiStruct_ColumnTempProfilePage_60:
            DisplayColumnTempProfilePageData(mustUpdateDisplay, CONVENTIONAL_COLUMN, GuiStruct_ColumnTempProfilePage_60);
            break;
        case GuiStruct_ColumnDHAutoCalibrationPage_Def:
            DisplayColumnDHAutoCalibrationPageData(mustUpdateDisplay);
            break;
        case GuiStruct_ColumnDHManualCalibrationPage_Def:
            DisplayColumnDHManualCalibrationPageData(mustUpdateDisplay);
            break;
        case GuiStruct_ColumnDHSensorCalibration_Def:
            DisplayColumnDHSensorCalibrationPageData(mustUpdateDisplay);
            break;
        case GuiStruct_PSU_DAC_Page_Def:
            DisplayColumnDHPSUDACPageData(mustUpdateDisplay);
            break;
        case GuiStruct_ColumnOvenNudgeAndDampPage_0:
            DisplayColumnOvenNudgeAndDampPageData(mustUpdateDisplay);
            break;
        case GuiStruct_ColumnDHNudgeAndDampPage_0:
            DisplayColumnDHNudgeAndDampPageData(mustUpdateDisplay);
            break;
        case GuiStruct_InjectorNudgeAndDampPage_0:
            DisplayInjectorNudgeAndDampPageData(mustUpdateDisplay);
            break;
        case GuiStruct_DetectorNudgeAndDampPage_0:
            DisplayDetectorNudgeAndDampPageData(mustUpdateDisplay);
            break;
        case GuiStruct_AuxiliaryNudgeAndDampPage_0:
            DisplayAuxiliaryNudgeAndDampPageData(mustUpdateDisplay);
            break;
        case GuiStruct_FanPowerPage_0:
            DisplayFanPowerPageData(mustUpdateDisplay);
            break;
        case GuiStruct_DebugCommandsPage_Def:
            DisplayDebugCommandsPageData(mustUpdateDisplay);
            break;
        case GuiStruct_InjectorPage1_3:
            DisplayInjectorPageData(mustUpdateDisplay);
            break;
        case GuiStruct_InjectorMethodPage_Def:
            DisplayInjectorMethodPageData(mustUpdateDisplay);
            break;
        case GuiStruct_InjectorTempProfilePage_25:
            DisplayInjectorTempProfilePageData(mustUpdateDisplay);
            break;
        case GuiStruct_DetectorFIDPage_4:
            DisplayDetectorPageData(mustUpdateDisplay, FID_DETECTOR, GuiStruct_DetectorFIDPage_4);
            break;
        case GuiStruct_DetectorECDPage_12:
            DisplayDetectorPageData(mustUpdateDisplay, ECD_DETECTOR, GuiStruct_DetectorECDPage_12);
            break;
        case GuiStruct_DetectorFPDPage_14:
            DisplayDetectorPageData(mustUpdateDisplay, FPD_DETECTOR, GuiStruct_DetectorFPDPage_14);
            break;
        case GuiStruct_DetectorNPDPage_28:
            DisplayDetectorPageData(mustUpdateDisplay, NPD_DETECTOR, GuiStruct_DetectorNPDPage_28);
            break;
        case GuiStruct_DetectorNonePage_31:
            DisplayDetectorPageData(mustUpdateDisplay, NO_DETECTOR, GuiStruct_DetectorNonePage_31);
            break;
        case GuiStruct_DetectorPIDPage_29:
            DisplayDetectorPageData(mustUpdateDisplay, PID_DETECTOR, GuiStruct_DetectorPIDPage_29);
            break;
        case GuiStruct_DetectorSPDIDPage_30:
            DisplayDetectorPageData(mustUpdateDisplay, SPDID_DETECTOR, GuiStruct_DetectorSPDIDPage_30);
            break;
        case GuiStruct_DetectorTCDPage_11:
            DisplayDetectorPageData(mustUpdateDisplay, TCD_DETECTOR, GuiStruct_DetectorTCDPage_11);
            break;
        case GuiStruct_GasInformationPage_6:
            DisplayGasInformationPageData(mustUpdateDisplay);
            break;
        case GuiStruct_GasMethodPage_Def:
            DisplayGasMethodPageData(mustUpdateDisplay);
            break;
        case GuiStruct_GasProfilePage_15:
            DisplayGasFlowProfilePageData(mustUpdateDisplay);
            break;
        case GuiStruct_GasCalibrationPage_Def:
            DisplayGasCalibrationPageData(mustUpdateDisplay);
            break;
        case GuiStruct_GasBackPressureDACPage_Def:
            DisplayGasBackPressureDACPageData(mustUpdateDisplay);
            break;
        case GuiStruct_GasChannelDACAndADCPage_Def:
            DisplayGasChannelDACAndADCPageData(mustUpdateDisplay);
            break;
        case GuiStruct_RunningPage1_7:
            DisplayRunningPageData(mustUpdateDisplay, false);
            break;
        case GuiStruct_SettingsPage_5:
            DisplaySettingsPageData(mustUpdateDisplay);
            break;
        case GuiStruct_RunningColumnPage_25:
            DisplayRunningColumnPageData(mustUpdateDisplay, false);
            break;
        case GuiStruct_RunningInjectorPage_26:
            DisplayRunningInjectorPageData(mustUpdateDisplay);
            break;
        case GuiStruct_RunningInjectorProfilePage_Def:
            DisplayRunningInjectorProfilePageData(mustUpdateDisplay, false);
            break;
        case GuiStruct_RunningDetectorPage_27:
            DisplayRunningDetectorPageData(mustUpdateDisplay);
            break;
        case GuiStruct_RunningGasPage_28:
            DisplayRunningGasPageData(mustUpdateDisplay, false);
            break;
        case GuiStruct_ServicingPage_Def:
            DisplayServicingPage();
            break;
        case GuiStruct_ServicingRequired_Def:
            DisplayServicingRequiredPage();
            break;
        default:
            // Page with no data (e.g. Gas Saver/Standby) - ignore
            break;
    }
    
    displayingData = false;
}

// This effectively duplicates the function of the same name in GCHeatControl - 
// it seems less complicated than calling GCHeatControl::IsHeatOn from here - 
// would need an instance of GCHeatControl in this class, etc - 
// why bother, since both would just call the same GC?
bool GetGCStatusLoop::IsHeatOn(void)
{
    char response[50];
    SetGCDeviceReport("QHTS", response);
    
    // Check for "EPKT" first
    if(response[0] == 'E') return false;
    
    return (response[7] != '0');
}

/*
    Gets the GC status code, and returns it as an integer.
    
    No arguments.
    
    Returns the status as an integer - see the GC_STATE enumeration (GCStateAndFaultCodes.h) 
    for the meaning of each value.
*/
int GetGCStatusLoop::GetInstrumentStatus(void)
{
    char response[50];
    SetGCDeviceReport("QSTA", response);
    
    // We expect a response of the form "DSTA00nn", where 'nn' is a two-digit code representing the status.
    // We convert those two digits to an integer, and return the result to the user
    
    // But check for "EPKT" first
#ifdef USE_VERSION_102 // See GCStateAndFaultCodes.h
    if(response[0] == 'E') return GC_STATE_102_METHOD_FAULTED;
#else
    if(response[0] == 'E') return GC_STATE_FAULTED;
#endif

    int retval;
    sscanf(&response[6], "%d", &retval);
//#define DEBUG_HERE
#ifdef DEBUG_HERE
    char dbg[100];
    sprintf(dbg, "GGCSL::GIS - returning : %d", retval);
    EasyGUIDebugPrint(dbg, 0, 20);
#undef DEBUG_HERE
#endif
    return retval;
}

/*
    Tells the caller whether or not the GC is running, based on its current state
    (note that there is not one single 'GC_IS_RUNNING' state).
    
    Returns true if the GC is running, false if not.
*/
bool GetGCStatusLoop::GCIsRunning(void)
{
    int currentGCState = GetInstrumentStatus();
    
    return(GCStateOrFaultCode::GetSimplifiedGCState(currentGCState) == GC_RUNNING);
}


/*
    Tells the caller whether or not the GC is in the 'stabilising' state
    
    Returns true if the GC is ing, false if not.
*/
bool GetGCStatusLoop::GCIsStabilising(void)
{
    int currentGCState = GetInstrumentStatus();
    
    return(GCStateOrFaultCode::GetSimplifiedGCState(currentGCState) == GC_STABILISING);
}


/*
    Returns the status of the specified component.
    
    Args: the component whose status is to be obtained, as a value in the GCComponent enumeration.
    
    Return value: the status of the specified component, as a value in the GCComponentStatus enumeration.
    
    The GCComponent and GCComponentStatus enumerations are defined in GCComponentStatusEnums.h.
*/
GCComponentStatus GetGCStatusLoop::GetComponentStatus(GCComponent component)
{
    int currentGCState = GetInstrumentStatus();
    GCStateSimplified simplifiedGCState = GCStateOrFaultCode::GetSimplifiedGCState(currentGCState);
    
    if(simplifiedGCState == GC_RUNNING) {
        return READY; // It must be ready, if the GC is running
    }         
    // 'else'...

    switch (simplifiedGCState) {
        case GC_READY_TO_RUN:
            return READY;
        case GC_FAULTED:
            return FAULTED;
        default:
            break; // Fall through to code below...
    }
    
    char buff[100];
    float temperature, maxTemperature;
    float pressure;
    
    switch (component) {
        case COLUMN:
            GetColumnTemperature(&temperature);
            if(temperature < 0.0f) {// Got "EPKT" response
                return FAULTED;
            }
            if(temperature < 0.1f) { // Allow for floating-point rounding errors
                return COLD;
            }
            GetColumnMaxTemperature(&maxTemperature);
            if(temperature < maxTemperature) {
                return HEATING_UP;
            } else {
                return READY;
            }
        case INJECTOR:
            GetInjectorTemperature(&temperature);
            if(temperature < 0.0f) {// Got "EPKT" response
                return FAULTED;
            }
            if(temperature < 0.1f) { // Allow for floating-point rounding errors
                return COLD;
            }
            //TODO: Fill in...
            return HEATING_UP;
        case DETECTOR:
            GetDetectorTemperature(&temperature);
            if(temperature < 0.0f) {// Got "EPKT" response
                return FAULTED;
            }
            if(temperature < 0.1f) { // Allow for floating-point rounding errors
                return COLD;
            }
            //TODO: Fill in...
            return HEATING_UP;
        case GAS:
            if(GetGasControlMode(buff, false, false) == false) {
                // Got "EPKT"
                return FAULTED;
            }
            GetGasPressure(&pressure);
            if(pressure < 0.0f) {// Got "EPKT" response
                return FAULTED;
            }
            if(pressure < 0.1f) { // Allow for floating-point rounding errors
                return COLD;
            }
            //TODO: Fill in...
            return HEATING_UP;
        default:
            sprintf(buff, "Unknown component: %d", component);
            EasyGUIDebugPrint(buff, 0, 20);
            break;
    }

    return FAULTED;
}

/*
    Gets the status of the specified component, amd returns an appropriate null-terminated descriptive string
    in the buffer provided
    
    Note also that this code is intended to be as efficient as possible.
    
    Args: the component whose status is to be obtained, as a value in the GCComponent enumeration.
          pointer to the buffer to contain the null-terminated descriptive string
    
    The GCComponent and GCComponentStatus enumerations are defined in GCComponentStatusEnums.h.
    
    No return value.
*/
void GetGCStatusLoop::GetComponentStatusString(GCComponent component, char *buff)
{
    int index = 0;
    switch (GetComponentStatus(component)) {
        case COLD:
            buff[index++] = 'C';
            buff[index++] = 'o';
            buff[index++] = 'l';
            buff[index++] = 'd';
            break;
        case HEATING_UP:
            if(IsHeatOn()) {
                if(GCIsStabilising()) {
                    buff[index++] = 'S';
                    buff[index++] = 't';
                    buff[index++] = 'a';
                    buff[index++] = 'b';
                    buff[index++] = 'i';
                    buff[index++] = 'l';
                    buff[index++] = 'i';
                    buff[index++] = 's';
                    buff[index++] = 'i';
                    buff[index++] = 'n';
                    buff[index++] = 'g';
                } else {
                    // Assume it is equilibrating
                    buff[index++] = 'E';
                    buff[index++] = 'q';
                    buff[index++] = 'u';
                    buff[index++] = 'i';
                    buff[index++] = 'l';
                    buff[index++] = 'i';
                    buff[index++] = 'b';
                    buff[index++] = 'r';
                    buff[index++] = 'a';
                    buff[index++] = 't';
                    buff[index++] = 'i';
                    buff[index++] = 'n';
                    buff[index++] = 'g';
                }
            } else {
                buff[index++] = 'I';
                buff[index++] = 'd';
                buff[index++] = 'l';
                buff[index++] = 'e';
            }
            break;
        case READY:
            buff[index++] = 'R';
            buff[index++] = 'e';
            buff[index++] = 'a';
            buff[index++] = 'd';
            buff[index++] = 'y';
            break;        
        default: // Including FAULTED
            buff[index++] = 'F';
            buff[index++] = 'a';
            buff[index++] = 'u';
            buff[index++] = 'l';
            buff[index++] = 't';
            buff[index++] = 'e';
            buff[index++] = 'd';
            break;
    }
    buff[index++] = '\0';
}


/*
    Returns true if the GC is in standby mode, false if not.
*/
bool GetGCStatusLoop::GCIsInStandbyMode(void)
{
    char response[50];
    SetGCDeviceReport("QDIS", response);
    
    // We expect a response of the form "DDIS000n", where 'n' == '0' means the GC is in standby mode,
    // while 'n' == '1' means that it is not in standby mode

    bool retval = (response[7] == '0');

    // Also check for "EPKT" 
    if(response[0] == 'E') retval = false;
    
//#define DEBUG_HERE
#ifdef DEBUG_HERE
    if(retval) {
        EasyGUIDebugPrint("GGCSL::GCIISM - returning true", 0, 20);
    } else {
        EasyGUIDebugPrint("GGCSL::GCIISM - returning false", 0, 20);
    }
#endif
//#undef DEBUG_HERE
    return retval;
}

/*
    Caller uses this to tell us that the GC has exited from standby mode
*/
void GetGCStatusLoop::ExitedGCStandbyMode(void)
{
    gcInStandbyMode = false;
}

/*
    Takes the GC out of standby mode, by passing it the "CDIS" command.
    
    Returns true if the GC returned "DACK" in response, false if it returned "DNAK".
*/
bool GetGCStatusLoop::ExitGCStandbyMode(void)
{
    if(ExecuteCommandWithDACKResponse("CDIS")) {
 
        ExitedGCStandbyMode();
    
        return true;
    }
    
    return false;
}

/*
    Displays the standby mode page (easyGUI "structure")
*/
void GetGCStatusLoop::DisplayStandbyModePage(void)
{
#ifdef USING_BACKGROUND_BITMAP
    DrawBackgroundBitmap(); 
#else
    GuiLib_Clear();
#endif

    GuiLib_ShowScreen(GuiStruct_GasSaver_9, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);

    GuiLib_Refresh();
    
#define DEBUG_HERE
#ifdef DEBUG_HERE
    char dbg[100];
    sprintf(dbg, "After GuiLib_Clear 7");
    EasyGUIDebugPrint(dbg, 0, 20);
#undef DEBUG_HERE
#endif
        
    SetCurrentPage(GuiStruct_GasSaver_9);
}

/*
    Displays the easyGUI page (or "structure") that shows the user that the GC has faulted
*/
void GetGCStatusLoop::DisplayGCInFaultStatePage(bool clearDisplay)
{
    if(clearDisplay) {
#ifdef USING_BACKGROUND_BITMAP
        DrawBackgroundBitmap(); 
#else
        GuiLib_Clear();
#endif
    }

    // Display red background to the error message, same as for a single component page
    GCComponentStatusColorArea::DisplayErrorRectangle();
        
    GuiLib_ShowScreen(GuiStruct_GCInFaultStatePage_11, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);
    
    GuiLib_Refresh();

#define DEBUG_HERE
#ifdef DEBUG_HERE
    char dbg[100];
    sprintf(dbg, "After GuiLib_Clear 8");
    EasyGUIDebugPrint(dbg, 0, 20);
#undef DEBUG_HERE
#endif
        
    SetCurrentPage(GuiStruct_GCInFaultStatePage_11);
}

void GetGCStatusLoop::DisplayRunCompletePage(void)
{
#ifdef USING_BACKGROUND_BITMAP
    DrawBackgroundBitmap(); 
#else
    GuiLib_Clear();
#endif

    GuiLib_ShowScreen(GuiStruct_RunCompletePage_41, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);
    
    GuiLib_Refresh();
    
    SetCurrentPage(GuiStruct_RunCompletePage_41);
}

void GetGCStatusLoop::DisplayRunAbortedPage(void)
{
#ifdef USING_BACKGROUND_BITMAP
    DrawBackgroundBitmap(); 
#else
    GuiLib_Clear();
#endif

    GuiLib_ShowScreen(GuiStruct_RunAbortedPage_Def, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);
    
    GuiLib_Refresh();
    
    SetCurrentPage(GuiStruct_RunAbortedPage_Def);
}

/*
    Takes the GC out of its error state, by passing it the "CCLR" command.
    
    Returns true if the GC returned "DACK" in response, false if it returned "DNAK".
*/
bool GetGCStatusLoop::ClearGCErrors(void)
{
    return ExecuteCommandWithDACKResponse("CCLR");
}

/*
    Aborts the current GC run, by passing it the "CABT" command.
    
    Returns true if the GC returned "DACK" in response, false if it returned "DNAK".
*/
bool GetGCStatusLoop::AbortGCRun(void)
{
    return ExecuteCommandWithDACKResponse("CABT");
}


/*
    Make sure that the component statuses, recorded in each of the colour areas for the home page,
    are up to date.
*/
void GetGCStatusLoop::UpdateHomePageGCComponentStatusColorAreas(void)
{
    GCComponentStatus columnStatus = GetComponentStatus(COLUMN);
    GCComponentStatus injectorStatus = GetComponentStatus(INJECTOR);
    GCComponentStatus detectorStatus = GetComponentStatus(DETECTOR);
    GCComponentStatus gasStatus = GetComponentStatus(GAS);
    
    if(homePageGCComponentStatusColorAreas != NULL) {
        homePageGCComponentStatusColorAreas->SetGCComponentStatus(COLUMN, columnStatus);
        homePageGCComponentStatusColorAreas->SetGCComponentStatus(INJECTOR, injectorStatus);
        homePageGCComponentStatusColorAreas->SetGCComponentStatus(DETECTOR, detectorStatus);
        homePageGCComponentStatusColorAreas->SetGCComponentStatus(GAS, gasStatus);
    }
}

/*
    Returns true if the current status of any of the components on the home page
    is now different to that displayed in the relevant status rectangle
    *** when we last displayed it ***
*/
bool GetGCStatusLoop::HomePageGCComponentStatusesHaveChanged(void)
{
    if(homePageGCComponentStatusColorAreas != NULL) {
        if(GetComponentStatus(COLUMN) != lastColumnStatusDisplayedOnHomePage) {
            return true;
        }
        
        if(GetComponentStatus(INJECTOR) != lastInjectorStatusDisplayedOnHomePage) {
            return true;
        }
        
        if(GetComponentStatus(DETECTOR) != lastDetectorStatusDisplayedOnHomePage) {
            return true;
        }
        
        if(GetComponentStatus(GAS) != lastGasStatusDisplayedOnHomePage) {
            return true;
        }
    }

    return false;    
}

/*
    Make sure that the component status, recorded in the colour area for a single component page,
    is up to date.
    
    Args: the component whose colour area is to be updated, as a value in the GCComponent enumeration.
    
    The GCComponent enumeration is defined in GCComponentStatusEnums.h.
*/
void GetGCStatusLoop::UpdateSingleGCComponentPageStatusColorArea(GCComponent component)
{
    GCComponentStatus componentStatus = GetComponentStatus(component);
    
    if(singleGCComponentPageStatusColorAreas != NULL) {
        singleGCComponentPageStatusColorAreas->SetGCComponentStatus(component, componentStatus);
    }
}

/*
    Update all the component status colour areas (i.e. the home page colour areas
    and the single component page colour areas), so they all match
*/
void GetGCStatusLoop::UpdateAllGCComponentStatusColorAreas(void)
{
    GCComponentStatus columnStatus = GetComponentStatus(COLUMN);
    GCComponentStatus injectorStatus = GetComponentStatus(INJECTOR);
    GCComponentStatus detectorStatus = GetComponentStatus(DETECTOR);
    GCComponentStatus gasStatus = GetComponentStatus(GAS);
    
    if(homePageGCComponentStatusColorAreas != NULL) {
        homePageGCComponentStatusColorAreas->SetGCComponentStatus(COLUMN, columnStatus);
        homePageGCComponentStatusColorAreas->SetGCComponentStatus(INJECTOR, injectorStatus);
        homePageGCComponentStatusColorAreas->SetGCComponentStatus(DETECTOR, detectorStatus);
        homePageGCComponentStatusColorAreas->SetGCComponentStatus(GAS, gasStatus);
    }
    
    if(singleGCComponentPageStatusColorAreas != NULL) {
        singleGCComponentPageStatusColorAreas->SetGCComponentStatus(COLUMN, columnStatus);
        singleGCComponentPageStatusColorAreas->SetGCComponentStatus(INJECTOR, injectorStatus);
        singleGCComponentPageStatusColorAreas->SetGCComponentStatus(DETECTOR, detectorStatus);
        singleGCComponentPageStatusColorAreas->SetGCComponentStatus(GAS, gasStatus);
    }
}

/*
    Returns true if the status of the specified component is now different 
    to that recorded in the status rectangle for the relevant single component page
*/
bool GetGCStatusLoop::SinglePageGCComponentStatusHasChanged(GCComponent component)
{
    if(singleGCComponentPageStatusColorAreas != NULL) {
        if(GetComponentStatus(component) != singleGCComponentPageStatusColorAreas->GetGCComponentStatus(component)) {
            return true;
        }
    }

    return false;    
}
    
/*
    Returns true if the status of the specified component is now different 
    to the last status displayed in the relevant status rectangle
*/
bool GetGCStatusLoop::SinglePageGCComponentStatusHasChanged(GCComponent component, GCComponentStatus lastStatusDisplayed)
{
    if(GetComponentStatus(component) != lastStatusDisplayed) {
        return true;
    }

    return false;    
}
    
/*
    Set up (i.e. get from the GC) the values for all the EasyGUI variables used by the various pages/structures we display.
    Caller should do this before entering our main loop - this ensures these variables are set up and ready
    before each of the pages is displayed

    No arguments.
    
    No return code.
*/
void GetGCStatusLoop::SetupAllEasyGUIVariables(void)
{
    // Set up (i.e. get from the GC) the values for all the EasyGUI variables used by the various pages/structures we display.
    // Caller should do this before entering our main loop - this ensures these variables are set up and ready
    // before each of the pages is displayed

    // Home page
#define SETUP_HOME_PAGE
#ifdef SETUP_HOME_PAGE
    if(GetColumnType() == DIRECTLY_HEATED_COLUMN) {
        GetDirectlyHeatedColumnTemperature(GuiVar_columnTemperature2, false);
    } else {
        GetColumnTemperature(GuiVar_columnTemperature2, false);
    }
    GetColumnTargetTemperature(GuiVar_columnTargetTemperature, "(Target: %s)");
    GetDetectorTemperature(GuiVar_detectorTemperature2);
    GetDetectorTargetTemperature(GuiVar_detectorTargetTemperature, "(Target: %s)");
    GetInjectorTemperature(GuiVar_injectorTemperature2, false);
    GetInjectorTargetTemperature(GuiVar_injectorTargetTemperature, "(Target: %s)");
    GetCurrentGasPressure(GuiVar_gasPressure2, false, true);
    GetGCRealTimeClockTime(GuiVar_gcTimeOnHomePage);
#undef SETUP_HOME_PAGE
#endif
    // We were omitting the home page variables here, on the theory that the home page is the first one displayed, 
    // so its variables will get updated anyway, and if we set them here, the page does not get updated when first displayed. 
    // However, this no longer appears to be true (and I cannot now remember why I thought it was). 
    // If we do *not* update these variables here, then we see the component status rectangles in their correct colours at startup, 
    // but the text does not appear for several seconds. If we *do* update these variables, the text is displayed at startup
    // at the same time as the rectangles, i.e. we get the behaviour we want.
        
    // Column page
    GetColumnTemperature(GuiVar_columnTemperature, false, true);
    GetColumnTargetTemperature(GuiVar_columnTargetTemperature2, stringFormatdegCUnits);
    GetColumnMaxTemperature(GuiVar_columnMaxTemp2, false, false, true);
    GetComponentStatusString(COLUMN, GuiVar_columnStatus);
    
    // Column Information page
    GetColumnType(GuiVar_columnType);
    GetColumnLength(GuiVar_columnLength);
    GetColumnInnerDiameter(GuiVar_columnInnerDiameter);
    GetColumnOuterDiameter(GuiVar_columnOuterDiameter);
    GetSerialNumber(GuiVar_columnSerialNumber);
           
    // Column Method page
    UpdateColumnMethodPageData();

    // Injector page
    GetInjectorTemperature(GuiVar_injectorTemperature, false);
    GetInjectorTargetTemperature(GuiVar_injectorTargetTemperature2, stringFormatdegCUnits);
    GetInjectionMode(GuiVar_injectionMode2);
    GetInjectorType(GuiVar_injectorType, false);
    GetComponentStatusString(INJECTOR, GuiVar_injectorStatus);
    
    // Injector Method page
    UpdateInjectorMethodPageData();

    // Detector page(s)
    GetDetectorType(GuiVar_detectorType2, false, false);    
    GetDetectorRange(GuiVar_detectorRange);
    GetFuelFlowRate(GuiVar_detectorFuelFlowRate);    
    GetAirFlowRate(GuiVar_detectorAirFlowRate);    
    GetDetectorIgnitionState(GuiVar_detectorStatus);
    GetDetectorTemperature(GuiVar_detectorTemperature);
    GetDetectorTargetTemperature(GuiVar_detectorTargetTemperature2, stringFormatdegCUnits);
    GetTCDDetectorFilamentPolarity(GuiVar_tcdDetectorFilamentPolarity);
    GetTCDDetectorFilamentTemperature(GuiVar_tcdDetectorFilamentTemperature);
    GetTCDDetectorRange(GuiVar_tcdDetectorRange);
    GetECDDetectorCurrent(GuiVar_ecdDetectorCurrent);
    //GetFPDDetectorRange(GuiVar_fpdDetectorRange);
    // No - now same command as all other detector types
    GetDetectorRange(GuiVar_fpdDetectorRange);
    GetFPDDetectorSensitivity(GuiVar_fpdDetectorSensitivity);
    
    // Gas page
    GetGasPressure(GuiVar_gasPressure, false, true);
    GetGasPulsedPressure(GuiVar_gasPulsedPressure);
    GetGasControlMode(GuiVar_gasControlMode2, true, false);
    GetCarrierGasType(GuiVar_gasCarrierType);
    GetGasFilterDateChanged(GuiVar_gasFilterDateChanged);    
    
    // Gas Method page
    UpdateGasMethodPageData();

    // Settings page
    GetGCSoftwareVersion(GuiVar_gcSoftwareVersion);
    GetActuatorSoftwareVersion(GuiVar_actuatorSoftwareVersion);
    GetRunTime(GuiVar_runTime);    
//    GetInjectionMode(GuiVar_injectionMode, true);
    GetInjectorType(GuiVar_injectionMode, true);
    GetColumnMaxTemperature(GuiVar_columnMaxTemp, true, true);
    GetDetectorType(GuiVar_detectorType, true, true);
    GetGasControlMode(GuiVar_gasControlMode, true, true);
    GetSerialNumber(GuiVar_gcSerialNumber);    
    GetGCRealTimeClockTime(GuiVar_gcTime);

    // Servicing page
    SetupServicingPageEasyGUIVariables();
}


/*
    Tells the caller whether or not a specified GC command is (potentially)
    part of a SendMethod sequence.
    
    Params: pointer to a null-terminated string containing the command in question
    
    Returns true if the command is one that is commonly part of a method specification,
    false if not.
    
    This code is intended to be as efficient as possible.
*/
bool GetGCStatusLoop::IsSendMethodCommand(char *gcCommand)
{
    // First of all - must be an "Sxxx" (i.e. set) command
    if(gcCommand[0] != 'S') {
        // Cannot be a 'set' command
        return false;
    }
    
    if((gcCommand[1] == 'C') && (gcCommand[2] == 'O') && (gcCommand[3] == 'L')) {
        // "SCOL" command
        return true;
    }
    
    if((gcCommand[1] == 'I') && (gcCommand[2] == 'N') && (gcCommand[3] == 'J')) {
        // "SINJ" command
        return true;
    }
    
    if((gcCommand[1] == 'D') && (gcCommand[2] == 'E') && (gcCommand[3] == 'T')) {
        // "SDET" command
        return true;
    }
    
    if((gcCommand[1] == 'A') && (gcCommand[2] == 'U') && (gcCommand[3] == 'X')) {
        // "SAUX" command
        return true;
    }
    
    if((gcCommand[1] == 'S') && (gcCommand[2] == 'T') && (gcCommand[3] == 'B')) {
        // "SSTB" command
        return true;
    }
    
    if((gcCommand[1] == 'T') && (gcCommand[2] == 'I') && (gcCommand[3] == 'M')) {
        // "STIM" command
        return true;
    }
    
    if((gcCommand[1] == 'R') && (gcCommand[2] == 'P')) {
        // "SRPn" command
        return true;
    }
    
    if((gcCommand[1] == 'R') && (gcCommand[2] == 'C')) {
        // "SRCn" command
        return true;
    }
    
    if((gcCommand[1] == 'R') && (gcCommand[2] == 'S')) {
        // "SRSn" command
        return true;
    }
    
    if((gcCommand[1] == 'P') && (gcCommand[2] == 'R')) {
        // "SPRS" or "SPRn" command
        return true;
    }
    
    if((gcCommand[1] == 'P') && (gcCommand[2] == 'P') && (gcCommand[3] == 'S')) {
        // "SPPS" command
        return true;
    }
    
    if((gcCommand[1] == 'P') && (gcCommand[2] == 'U')) {
        // "SPUn" command
        return true;
    }
    
    if((gcCommand[1] == 'I') && (gcCommand[2] == 'M') && (gcCommand[3] == 'D')) {
        // "SIMD" command
        return true;
    }
    
    if((gcCommand[1] == 'S') && (gcCommand[2] == 'P') && (gcCommand[3] == 'T')) {
        // "SSPT" command
        return true;
    }
    
    if((gcCommand[1] == 'C') && (gcCommand[2] == 'F') && (gcCommand[3] == 'L')) {
        // "SCFL" command
        return true;
    }
    
    if((gcCommand[1] == 'T') && (gcCommand[2] == 'F') && (gcCommand[3] == 'L')) {
        // "STFL" command
        return true;
    }
    
    if((gcCommand[1] == 'M') && (gcCommand[2] == 'A') && (gcCommand[3] == 'K')) {
        // "SMAK" command
        return true;
    }
    
    if((gcCommand[1] == 'C') && (gcCommand[2] == 'L') && (gcCommand[3] == 'B')) {
        // "SCLB" command
        return true;
    }
    
    return false; // None of the above
}


/*
    Forces the MainLoopWithEthernet function to exit its while loop
    and reboot the whole application
*/
void GetGCStatusLoop::ForceRestart(void)
{
    restartRequired = true;
}


/*
    Deals with the user touching the screen. Works out if the user touched an area
    we are interested in, and if so, handles it appropriately.
    
    Args: the X, Y and Z cordinates where the user touched (the touch panel gives us 
          all three coordinates, although currently we do not use the Z coordinate).
    
    No return code.
    
    The intention is that this function will, in effect, supersede the 'TouchCallback' function in main.cpp.
    For now, we are calling that function from here.
    
    Also note that this function sets 'handlingTouchEvent' true while it is dealing with the touch event.
    Caller should not call this function if this flag is true.
*/
void GetGCStatusLoop::HandleTouchEvent(short touchX, short touchY, short touchZ, int debugValue)
{
    handlingTouchEvent = true;
    
#if defined MULTI_TOUCH_TECHNIQUE_1
    // Enforce a minimum time interval between the touch events we respond to 
    if((timerTickCount - lastTouchEventTickCount) > minTimerTicksBetweenTouchEvents) {
#endif
#if defined MULTI_TOUCH_TECHNIQUE_2
    // Assume we are unlikely to get separate 'touches' in succession at exactly the same coordinates
    //if((x != lastTouchEventX) && (y != lastTouchEventY)) { // was '||', i.e. OR, not AND - we still got 'multiple touches'
    //Try and apply a minimum 'movement'...
    if((abs(touchX - lastTouchEventX) > 2) || (abs(touchY - lastTouchEventY) > 2)) { // Note the '||' - we miss too many 'touches' if we use '&&'
#endif
#if defined MULTI_TOUCH_TECHNIQUE_3
    if(gotAtLeastOneTimeout) {
#endif
#if defined MULTI_TOUCH_TECHNIQUE_4
    if(touchTimer.read_ms() > timerIntervalMilliSec) {
#endif
        char dbg[200];
        sprintf(dbg, "*** Got touch event at %d, %d, %d, %d ***", touchX, touchY, touchZ, timerTickCount);
        EasyGUIDebugPrintWithCounter(dbg, 430, 450);
                
        touch_coordinate_t touchCoords;
        touchCoords.x = touchX;
        touchCoords.y = touchY;
        touchCoords.z = touchZ;
        
        TouchCallback(touchCoords, usbDevice, usbHostGC, 1, true);
            
#if defined MULTI_TOUCH_TECHNIQUE_1
        lastTouchEventTickCount = timerTickCount;
    }
#endif
#if defined MULTI_TOUCH_TECHNIQUE_2
        lastTouchEventX = touchX;
        lastTouchEventY = touchY;
    }
#endif
#if defined MULTI_TOUCH_TECHNIQUE_3
        gotAtLeastOneTimeout = false;
    }
#endif
#if defined MULTI_TOUCH_TECHNIQUE_4
        touchTimer.stop();
        touchTimer.reset();
        touchTimer.start();
    }
#endif
    
    handlingTouchEvent = false;
}

/*
    Tells the caller if the specified command was a Run command,
    and if it succeeded - i.e. the command was "CRUN", and 
    the GC responded with "DACK".
    
    Args: pointers to null-terminated strings containing the command in question,
          and the GC response
          
    True if it was a "CRUN" command, and the response was "DACK", false otherwise
*/
bool GetGCStatusLoop::SuccessfulRunCommand(char* gcCommand, char* gcResponse)
{
    if(gcCommand[0] != 'C') {
        return false;
    }

    if(gcCommand[1] != 'R') {
        return false;
    }

    if(gcCommand[2] != 'U') {
        return false;
    }

    if(gcCommand[3] != 'N') {
        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;
}


/*
    Tells the caller if the specified command was an Abort command,
    and if it succeeded - i.e. the command was "CABT" or "CSTP"
    (currently these two GC commands are equivalent), 
    and the GC responded with "DACK".
    
    Args: pointers to null-terminated strings containing the command in question,
          and the GC response
          
    True if it was a "CRUN" command, and the response was "DACK", false otherwise
*/
bool GetGCStatusLoop::SuccessfulAbortCommand(char* gcCommand, char* gcResponse)
{
    if(gcCommand[0] != 'C') {
        // Not a command at all - give up
        return false;
    }
    
    bool wasAbortCommand = false;
    
    if((gcCommand[1]== 'A') && (gcCommand[2]== 'B') && (gcCommand[3]== 'T')) {
        wasAbortCommand = true;
    } else if ((gcCommand[1]== 'S') && (gcCommand[2]== 'T') && (gcCommand[3]== 'P')) {
        wasAbortCommand = true;
    }
    
    if(!wasAbortCommand) {
        return false;
    }
    
        
    // Now the response...
        
    if(gcResponse[0] != 'D') {
        return false;
    }

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

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

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

    // Must have been an abort command, acknowledged with "DACK"
    return true;
}

/*
    Tells the caller if the specified page includes one or more component status rectangles,
    or a component status displayed as text, and will therefore need to be redisplayed 
    if the component status changes
    
    Args: the number of the page in question
    
    Returns: true if the page contains the status of one or more components, false if not
*/
bool GetGCStatusLoop::PageIncludesComponentStatus(int pageNumber)
{
    // Quicker to decide which pages do *not* include a component status...
    
    if(pageNumber == GuiStruct_AbortRunPage_19) {
        return false;
    }

//#define USING_REAL_BACKGROUND_BITMAP
#ifdef USING_REAL_BACKGROUND_BITMAP
    if(pageNumber == GuiStruct_BackgroundBitmapPage_Def) {
        return false;
    }
#endif // USING_REAL_BACKGROUND_BITMAP

    if(pageNumber == GuiStruct_DownloadingMethodPage_Def) {
        return false;
    }
    
    if(pageNumber == GuiStruct_EthernetConnectionPage_Def) {
        return false;
    }
    
    if(pageNumber == GuiStruct_EthernetParametersPage_50) {
        return false;
    }
    
    if(pageNumber == GuiStruct_GCConnectionPage_Def) {
        return false;
    }
    
    if(pageNumber == GuiStruct_GasSaver_9) {
        return false;
    }
    
    if(pageNumber == GuiStruct_RunAbortedPage_Def) {
        return false;
    }
    
    if(pageNumber == GuiStruct_RunCompletePage_41) {
        return false;
    }
    
    if(pageNumber == GuiStruct_RunningColumnPage_25) {
        return false;
    }
    
    if(pageNumber == GuiStruct_RunningDetectorPage_27) {
        return false;
    }
    
    if(pageNumber == GuiStruct_RunningGasPage_28) {
        return false;
    }
    
    if(pageNumber == GuiStruct_RunningInjectorPage_26) {
        return false;
    }
    
    if(pageNumber == GuiStruct_RunningPage1_7) {
        return false;
    }
    
    if(pageNumber == GuiStruct_ServicingPage_Def) {
        return false;
    }
    
    if(pageNumber == GuiStruct_ServicingRequired_Def) {
        return false;
    }
    
    if(pageNumber == GuiStruct_SettingsPage_5) {
        return false;
    }
    
    if(pageNumber == GuiStruct_NumericKeypadPage_Def) {
        return false;
    }
    
    // 'else' - none of the above
    return true;    
}


/*
    Should be done at startup, and after a new method has been downloaded
*/
void GetGCStatusLoop::UpdateColumnMethodPageData(void)
{
    GetColumnTargetTemperature(GuiVar_columnMethodInitialTemp, "%s");
    GetInitialHoldTime(GuiVar_columnMethodInitialHold);
    if(columnMethodRampData == NULL) {
        columnMethodRampData = new ColumnMethodRampData(usbDevice, usbHostGC);
    }
    if(columnMethodRampData != NULL) {
        columnMethodRampData->GetRampDataFromGC();
        
        sprintf(GuiVar_columnMethodRampCount, "%u", columnMethodRampData->GetRampCount());

        if(columnMethodRampData->NeedToUpdateEasyGUIMethodPageRampVariables()) {
            columnMethodPageScrollIndex = 0;
            
            columnMethodRampData->UpdateEasyGUIMethodPageVariables(columnMethodPageScrollIndex);
            
            previousColumnMethodPageScrollIndex = columnMethodPageScrollIndex;
        }
    }
}


/*
    Should be done at startup, and after a new method has been downloaded
*/
void GetGCStatusLoop::UpdateInjectorMethodPageData(void)
{
    GetInjectorTargetTemperature(GuiVar_injectorMethodInitialTemp, "%s");
    GetInitialHoldTime(GuiVar_injectorMethodInitialHold);
    if(injectorMethodRampData == NULL) {
        injectorMethodRampData = new InjectorMethodRampData(usbDevice, usbHostGC);
    }
    if(injectorMethodRampData != NULL) {
        injectorMethodRampData->GetRampDataFromGC();
        
        sprintf(GuiVar_injectorMethodRampCount, "%u", injectorMethodRampData->GetRampCount());

        if(injectorMethodRampData->NeedToUpdateEasyGUIMethodPageRampVariables()) {
            injectorMethodPageScrollIndex = 0;
            
            injectorMethodRampData->UpdateEasyGUIMethodPageVariables(injectorMethodPageScrollIndex);
            
            previousInjectorMethodPageScrollIndex = injectorMethodPageScrollIndex;
        }
    }
}


/*
    Should be done at startup, and after a new method has been downloaded
*/
void GetGCStatusLoop::UpdateGasMethodPageData(void)
{
    GetGasPressure(GuiVar_gasMethodInitialPressure, false, false);
    GetInitialHoldTime(GuiVar_gasMethodInitialHold);
    if(gasMethodRampData == NULL) {
        gasMethodRampData = new GasMethodRampData(usbDevice, usbHostGC);
    }
    if(gasMethodRampData != NULL) {
        gasMethodRampData->GetRampDataFromGC();
        
        sprintf(GuiVar_gasMethodRampCount, "%u", gasMethodRampData->GetRampCount());

        if(gasMethodRampData->NeedToUpdateEasyGUIMethodPageRampVariables()) {
            gasMethodPageScrollIndex = 0;
            
            gasMethodRampData->UpdateEasyGUIMethodPageVariables(gasMethodPageScrollIndex);
            
            previousGasMethodPageScrollIndex = gasMethodPageScrollIndex;
        }
    }
}


/*
    This function is called when the Ethernet handler has signaled us that it has 
    received a message from the Ethernet client. This function gets the message 
    from the Ethernet handler, passes it on to the GC, then passes the response back to the handler,
    to be passed back to the client. 
    
    Note that this function does no other processing on the message - 
    we simply assume that the message must be a GC command without looking at it.
*/
void GetGCStatusLoop::HandleEthernetMessage(Thread* ethernetThread, int debugValue)
{
    // TODO: What if 'handlingEthernetMessage' is already true??
    
    char gcCommand[256];
    char gcResponse[GC_MESSAGE_LENGTH + 2];
    int responseReadyThreadSignalCode;

    handlingEthernetMessage = true;
    
    EthernetHandler::GetGCCommand(gcCommand, &responseReadyThreadSignalCode);
    
    ethernetThread->signal_set(GOT_GC_COMMAND);       
    
//#define ALLOW_DEBUG_PRINTS_HERE
#ifdef ALLOW_DEBUG_PRINTS_HERE
    char buff[300];
    sprintf(buff, "[%d] Command sent to GC: \"%s\"", debugValue, gcCommand);
    SpecialDebugPrint(buff, 50, 50);
#endif // ALLOW_DEBUG_PRINTS_HERE
    
#ifdef USE_LED_FOR_DEBUGGING
    SetLed3(true);
#endif

    gcResponseTimer.reset();
    gcResponseTimer.start();
    
    SetGCDeviceReport(gcCommand, gcResponse);

    gcResponseTimer.stop();
    
#ifdef USE_LED_FOR_DEBUGGING
    SetLed3(false);
#endif
    
    // Turn on LED 2 while we are sending a method to the GC
    
    if(IsStartOfMethodCommand(gcCommand)) {
        // We have just started sending a method to the GC
#ifdef USE_LED_FOR_DEBUGGING
        SetLed2(true);
#endif

#ifdef DO_NOTHING_ELSE_WHILE_SENDING_METHOD
        sendingMethod = true;
        // This is not set true anywhere else - we only need this '#ifdef' here
#endif // DO_NOTHING_ELSE_WHILE_SENDING_METHOD
        
        DisplayDownloadingMethodPage();
    }
        
    if(IsEndOfMethodCommand(gcCommand)) {
        // We have just finished sending a method to the GC
#ifdef USE_LED_FOR_DEBUGGING
        SetLed2(false);
#endif        
        sendingMethod = false;
        
        UndisplayDownloadingMethodPage();

        //needToUpdateProfileGraphs = true;
        // Just do this here - don't wait - make sure graphs are updated 
        // *before* the user sees them
        SetupColumnAndInjectorAndGasProfileData();
        
        UpdateColumnMethodPageData();    
        UpdateInjectorMethodPageData();    
        UpdateGasMethodPageData();    
    }
    
    if(SuccessfulRunCommand(gcCommand, gcResponse)) {
        SetupForStartOfRun(usbDevice, usbHostGC);
    }
    
    if(SuccessfulAbortCommand(gcCommand, gcResponse)) {
        runWasAborted = true;
    }
        
#ifdef ALLOW_DEBUG_PRINTS_HERE
    sprintf(buff, "[%d] Response received from GC: \"%s\"", debugValue, gcResponse);
    SpecialDebugPrint(buff, 50, 100);

    if((gcResponse[1] == gcCommand[1]) && (gcResponse[2] == gcCommand[2]) && (gcResponse[3] == gcCommand[3])) {
        SpecialDebugPrint("Command and response match", 50, 120);
    } else {
        SpecialDebugPrint("Command and response *** do not match ***", 50, 120);
    }
    
    sprintf(buff, "[%d] Time taken by GC: %d ms", debugValue, gcResponseTimer.read_ms());
    SpecialDebugPrint(buff, 50, 140);    
#undef ALLOW_DEBUG_PRINTS_HERE
#endif // ALLOW_DEBUG_PRINTS_HERE

    // TODO: do this immediately after 'SetGCDeviceReport' above?
    
    EthernetHandler::SetGCResponse(gcResponse);
    
    ethernetThread->signal_set(responseReadyThreadSignalCode);       
    
    handlingEthernetMessage = false;
}

/*
    Attempt to speed up main thread's response to the Ethernet thread - 
    perform a short wait for Ethernet transactions at multiple points in the main loop.
*/
void GetGCStatusLoop::ShortEthernetThreadWait(Thread* ethernetThread, int debugValue)
{
    // Try and keep comms in sync
    if(!handlingEthernetMessage) {
        
        Thread::signal_wait(GC_COMMAND_READY, shortWaitTimeMs); // Wait for GC commands over Ethernet link, nothing else
    
        if(EthernetHandler::GCCommandReceived()) {
    
            EasyGUIDebugPrintWithCounter("*** Ethernet command received ***", 125, 405);
    
            HandleEthernetMessage(ethernetThread, debugValue);
        }
        // else must have timed out - do nothing
    }
}

/*
    Attempt to speed up main thread's response to touch events - 
    perform a short wait for signals from the touch thread at multiple points in the main loop.
*/
void GetGCStatusLoop::ShortTouchThreadWait(SimplifiedTouchListener* stl, int debugValue)
{
    if(!handlingTouchEvent) {
        
        Thread::signal_wait(TOUCH_EVENT, shortWaitTimeMs); // Wait for touch events (signaled by SimplifiedTouchListener 
                                                           // on touch thread), nothing else
        short x, y, z;
        if(stl->GotTouchEvent(&x, &y, &z)) {

//#define ALLOW_DEBUG_PRINTS_HERE
#ifdef ALLOW_DEBUG_PRINTS_HERE
            char buff[300];
            sprintf(buff, "[%d] Got touch event", debugValue);
            SpecialDebugPrint(buff, 400, 80);
#endif // ALLOW_DEBUG_PRINTS_HERE

            HandleTouchEvent(x, y, z, debugValue);
        }
        // else must have timed out - do nothing
    }
}


/*
    Attempt to speed up main thread's response to the Touch and Ethernet threads - 
    perform a short wait for signals from any thread at multiple points in the main loop.
*/
void GetGCStatusLoop::ShortThreadWait(SimplifiedTouchListener* stl, Thread* ethernetThread, int debugValue)
{
    osEvent signalWaitRetcode = Thread::signal_wait(0, shortWaitTimeMs); // Wait for any signal
    
//#define ALLOW_DEBUG_PRINTS_HERE
#ifdef ALLOW_DEBUG_PRINTS_HERE
    if(signalWaitRetcode.value.signals != 0) {
        char buff[300];
        sprintf(buff, "Short Thread::signal_wait returned %X", signalWaitRetcode.value.signals);
        SpecialDebugPrint(buff, 400, 50);
    }
#undef ALLOW_DEBUG_PRINTS_HERE
#endif // ALLOW_DEBUG_PRINTS_HERE
    
    switch(signalWaitRetcode.value.signals) {
        case TOUCH_EVENT:
            if(!handlingTouchEvent) {
                
                short x, y, z;
                if(stl->GotTouchEvent(&x, &y, &z)) {
                    
//#define ALLOW_DEBUG_PRINTS_HERE
#ifdef ALLOW_DEBUG_PRINTS_HERE
                    char buff[300];
                    sprintf(buff, "[%d] Got touch event", debugValue);
                    SpecialDebugPrint(buff, 400, 80);
#undef ALLOW_DEBUG_PRINTS_HERE
#endif // ALLOW_DEBUG_PRINTS_HERE

                    HandleTouchEvent(x, y, z, debugValue);
                }
            }
            break;        
                            
        case GC_COMMAND_READY:
            // Try and keep comms in sync
            if(!handlingEthernetMessage) {
                
                if(EthernetHandler::GCCommandReceived()) {
            
//#define ALLOW_DEBUG_PRINTS_HERE
#ifdef ALLOW_DEBUG_PRINTS_HERE
                    char buff[300];
                    sprintf(buff, "[%d] Got Ethernet event", debugValue);
                    SpecialDebugPrint(buff, 400, 80);
#undef ALLOW_DEBUG_PRINTS_HERE
#endif // ALLOW_DEBUG_PRINTS_HERE

                    HandleEthernetMessage(ethernetThread, debugValue);
                }
                // else must have timed out - do nothing
            }
            break;     
            
        case STARTED_DOWNLOADING_METHOD: 
            // Ethernet thread has signalled us that we have just started sending a method to the GC
#ifdef USE_LED_FOR_DEBUGGING
            // Turn on LED 2 while we are sending a method to the GC
            SetLed2(true);
#endif

#ifdef DO_NOTHING_ELSE_WHILE_SENDING_METHOD
            sendingMethod = true;
            // This is not set true anywhere else - we only need this '#ifdef' here
#endif // DO_NOTHING_ELSE_WHILE_SENDING_METHOD
        
            DisplayDownloadingMethodPage();
            break;   
                            
        case FINISHED_DOWNLOADING_METHOD:
            // Ethernet thread has signalled us that we have just finished sending a method to the GC
#ifdef USE_LED_FOR_DEBUGGING
            SetLed2(false);
#endif        
            sendingMethod = false;
            
            UndisplayDownloadingMethodPage();
    
            //needToUpdateProfileGraphs = true;
            // Just do this here - don't wait - make sure graphs are updated 
            // *before* the user sees them
            SetupColumnAndInjectorAndGasProfileData();
        
            UpdateColumnMethodPageData();
            UpdateInjectorMethodPageData();
            UpdateGasMethodPageData();
            break;   
            
        case CRUN_COMMAND_SENT:
            SetupForStartOfRun(usbDevice, usbHostGC);
            break;
                            
        case CHON_COMMAND_SENT:
            DisplayCurrentPageData(true);
            break;
                            
        case CHOF_COMMAND_SENT:
            if(realGCIsRunning) {
                runWasAborted = true;
            } else {
                DisplayCurrentPageData(true);
            }
            break;
                         
        case ABORT_RUN_COMMAND_SENT:
            if(realGCIsRunning) {
                runWasAborted = true;
            }   

        default: // Unknown signal or timeout - ignore
            break;
    }       
}


/*
    Find out and display the GC's current status
*/
void GetGCStatusLoop::PollGC(SimplifiedTouchListener* stl, Thread* ethernetThread)
{
    char statusString[100];
    
    if(GCIsInStandbyMode()) {
        if(!gcInStandbyMode) {
            gcInStandbyMode = true;
            
            DisplayStandbyModePage();
        }
    }
    
    //ShortEthernetThreadWait(ethernetThread, 2);
    //ShortTouchThreadWait(stl, 2);
    ShortThreadWait(stl, ethernetThread, 2);
    
    // Deal with GC status
    int gcStatus = GetGCStatus();
    if(GCHasFaulted(gcStatus, statusString)) {
        if(realGCIsRunning) {
            
            // GC has faulted during a run
            
            realGCIsRunning = false;
        
#ifdef SERVICE_INTERVALS_ACTIVE
            ServiceInterval::TellAllServiceIntervalsInstrumentHasCycled();
            // Some components need servicing based on how many cycles the instrument has performed
            // (others are time-based, i.e. every twelve months)
            if(ServiceInterval::AtLeastOneServiceIntervalHasExpired()) {
                DisplayServicingRequiredPage();
            }
#endif // SERVICE_INTERVALS_ACTIVE
        }
        
        //ShortEthernetThreadWait(ethernetThread, 3);
        //ShortTouchThreadWait(stl, 3);
        ShortThreadWait(stl, ethernetThread, 3);


        if((currentPage != GuiStruct_GCInFaultStatePage_11)
        || (strcmp(GuiVar_gcState, statusString) != 0)) {
            strcpy(GuiVar_gcState, statusString);
            
            DisplayGCInFaultStatePage(currentPage != GuiStruct_GCInFaultStatePage_11);
        }

        //ShortEthernetThreadWait(ethernetThread, 4);
        //ShortTouchThreadWait(stl, 4);
        ShortThreadWait(stl, ethernetThread, 4);
        

    } else { // GC has not faulted

        if(currentPage == GuiStruct_GCInFaultStatePage_11) {
            
            // No longer in fault state
            
            currentPage = GuiStruct_HomePage_1;
            
            lastSimplifiedGCState = GC_FAULTED;
            
            // The code below will now display the home page
            
            // ...but first, we need to do this - without it, the red rectangle (showing the error state)
            // is left in the background when we display the home page
#ifdef USING_BACKGROUND_BITMAP
            DrawBackgroundBitmap(); 
#else
            GuiLib_Clear();
#endif
        }

        //ShortEthernetThreadWait(ethernetThread, 5);
        //ShortTouchThreadWait(stl, 5);
        ShortThreadWait(stl, ethernetThread, 5);
    
        // Bug #4 fix - make sure GC status is up to date - we may have done something
        // to change it in 'ShortThreadWait'
        gcStatus = GetGCStatus();

        GCStateSimplified simplifiedGCState = GCStateOrFaultCode::GetSimplifiedGCState(gcStatus);
        
        char buff[200];
        sprintf(buff, "simplifiedGCState is: %d", simplifiedGCState);
        EasyGUIDebugPrintWithCounter(buff, 400, 300);
        
#ifdef UPDATE_PAGES_CONTINUOUSLY
        if(!handlingEthernetMessage) { // Try and keep comms in sync
#else
        if(simplifiedGCState != lastSimplifiedGCState) {
#endif // UPDATE_PAGES_CONTINUOUSLY           

            // Re-display all pages that display a status (heat on/off, column ready/equilibrating, etc)
            // - i.e. a coloured status rectangle, and/or descriptive status text
            if(PageIncludesComponentStatus(currentPage)) {
                DisplayCurrentPageData(true);
            }
            /*
            if(currentPage == GuiStruct_HomePage_1) {
                DisplayHomePageData(true);
            } else if(currentPage == GuiStruct_ColumnPage1_2) {
                DisplayColumnPageData(true, CONVENTIONAL_COLUMN, GuiStruct_ColumnPage1_2);
            } else if(currentPage == GuiStruct_ColumnDHPage1_40) {
                DisplayColumnPageData(true, DIRECTLY_HEATED_COLUMN, GuiStruct_ColumnDHPage1_40);
            } else if(currentPage == GuiStruct_InjectorPage1_3) {
                DisplayInjectorPageData(true);
            }
            */
        }
        
        if((simplifiedGCState == GC_RUNNING) && (!realGCIsRunning)) {
            // GC has started running without our knowledge 
            // (e.g. it can be started by a hardware trigger)
            SetupForStartOfRun(usbDevice, usbHostGC);
//#define BUG_4_ALLOW_DEBUG_PRINTS_HERE
#ifdef BUG_4_ALLOW_DEBUG_PRINTS_HERE
            SpecialDebugPrint("WOK run started", 150, 460);
            // WOK = "without our knowledge" -
            // mistakenly executing this code after the run has been aborted
            // causes bug #4 - but *why* is it executed?
            // Ans - because we get the GC status early on in this function,
            // and we call 'ShortThreadWait' several times between there and here.
            // If a 'ShortThreadWait' sees a touch on ABORT_RUN_YES, we will stop 
            // the run *after* getting the status in this function, and 
            // 'simplifiedGCState' will be out of date. In this case, we will see bug #4...
#undef BUG_4_ALLOW_DEBUG_PRINTS_HERE
#endif // BUG_4_ALLOW_DEBUG_PRINTS_HERE
        }

        lastSimplifiedGCState = simplifiedGCState;
    }
    // End of dealing with GC status
    
    //ShortEthernetThreadWait(ethernetThread, 6);
    //ShortTouchThreadWait(stl, 6);
    ShortThreadWait(stl, ethernetThread, 6);
    
    if(pageJustChanged) {
        // Don't display page data if it has just been done - leave till next time - 
        // (a) it's unnecessary, (b) it appears to cause random crashes
        pageJustChanged = false;
    } else {
        DisplayCurrentPageData(false);
    }
    
    //ShortEthernetThreadWait(ethernetThread, 7);
    //ShortTouchThreadWait(stl, 7);
    ShortThreadWait(stl, ethernetThread, 7);
}

    
/*
    Deal with the user interface and the Ethernet interface - both are signaled to us by other threads,
    so we need to use the same Thread::signal_wait for both.
    
    One of the most important functions in this application.
    ********************************************************
*/
void GetGCStatusLoop::HandleUserInterfaceAndEthernet(SimplifiedTouchListener* stl, Thread* ethernetThread)
{
#ifdef USE_THREAD_WAIT
//        Thread::wait(waitTimeMs); // Let other things happen
//        Thread::signal_wait(TOUCH_EVENT, waitTimeMs);
    osEvent signalWaitRetcode;
    
    if(sendingMethod) {
        // Try and make sending a method as fast as possible - ignore other events 
        // signalWaitRetcode = Thread::signal_wait(GC_COMMAND_READY); // Wait for GC commands over Ethernet link, nothing else -
        //                                                            // and don't timeout
        // No - the Ethernet thread, not this thread, now handles the USB traffic with the GC -
        // so while the download is in progress, wait only for the signal the Ethernet thread sends us 
        // to tell us it has finished downloading the method
        signalWaitRetcode = Thread::signal_wait(FINISHED_DOWNLOADING_METHOD);
    } else {
        signalWaitRetcode = Thread::signal_wait(0, waitTimeMs); // Wait for any signal, not just touch events
    }
//#define ALLOW_DEBUG_PRINTS_HERE
#ifdef ALLOW_DEBUG_PRINTS_HERE
    if(signalWaitRetcode.value.signals != 0) {
        char buff[300];
        sprintf(buff, "Main Thread::signal_wait returned %X", signalWaitRetcode.value.signals);
        SpecialDebugPrint(buff, 400, 50);
    }
#undef ALLOW_DEBUG_PRINTS_HERE
#endif // ALLOW_DEBUG_PRINTS_HERE
    
    if(signalWaitRetcode.value.signals == TOUCH_EVENT) {
        
        if(!handlingTouchEvent) {
            short x, y, z;
            if(stl->GotTouchEvent(&x, &y, &z)) {
//#define ALLOW_DEBUG_PRINTS_HERE
#ifdef ALLOW_DEBUG_PRINTS_HERE
                SpecialDebugPrint("[1] Got touch event", 400, 80);
#endif // ALLOW_DEBUG_PRINTS_HERE

                HandleTouchEvent(x, y, z, 1);
            }
        }
        
    } else if (signalWaitRetcode.value.signals == GC_COMMAND_READY) {
        if(EthernetHandler::GCCommandReceived()) {
        
            EasyGUIDebugPrintWithCounter("*** Ethernet command received ***", 125, 405);
    
            // Try and keep comms in sync
            if(!handlingEthernetMessage) {
                HandleEthernetMessage(ethernetThread, 1);
            }
            
#if defined MULTI_TOUCH_TECHNIQUE_3
            gotAtLeastOneTimeout = true; // i.e. 'one non touch event'
#endif
        }
        
    } else if (signalWaitRetcode.value.signals == STARTED_DOWNLOADING_METHOD) {
        
        // Ethernet thread has signaled us that we have just started sending a method to the GC
#ifdef USE_LED_FOR_DEBUGGING
        // Turn on LED 2 while we are sending a method to the GC
        SetLed2(true);
#endif

#ifdef DO_NOTHING_ELSE_WHILE_SENDING_METHOD
        sendingMethod = true;
        // This is not set true anywhere else - we only need this '#ifdef' here
#endif // DO_NOTHING_ELSE_WHILE_SENDING_METHOD
        
        DisplayDownloadingMethodPage();
        
    } else if (signalWaitRetcode.value.signals == FINISHED_DOWNLOADING_METHOD) {
        
        // Ethernet thread has signaled us that we have just finished sending a method to the GC
#ifdef USE_LED_FOR_DEBUGGING
        // Turn on LED 2 while we are sending a method to the GC
        SetLed2(false);
#endif        
        sendingMethod = false;

        UndisplayDownloadingMethodPage();
        
        //needToUpdateProfileGraphs = true;
        // Just do this here - don't wait - make sure graphs are updated 
        // *before* the user sees them
        SetupColumnAndInjectorAndGasProfileData();
        // ...but note that we do this *after* un-displaying the downloading method page - 
        // otherwise there is an annoying lag between Ellution saying it has finished sending the method
        // and our showing our normal UI again (and we hope the user will not want to look
        // at the profiles immediately after the download finishes)
        
        UpdateColumnMethodPageData();
        UpdateInjectorMethodPageData();
        UpdateGasMethodPageData();
        
    } else if (signalWaitRetcode.value.signals == CRUN_COMMAND_SENT) {
        SetupForStartOfRun(usbDevice, usbHostGC);
    } else if (signalWaitRetcode.value.signals == CHON_COMMAND_SENT) {
        DisplayCurrentPageData(true);
    } else if (signalWaitRetcode.value.signals == CHOF_COMMAND_SENT) {
        if(realGCIsRunning) {
            runWasAborted = true;
        } else {
            DisplayCurrentPageData(true);
        }
    } else if (signalWaitRetcode.value.signals == ABORT_RUN_COMMAND_SENT) {
        if(realGCIsRunning) {
            runWasAborted = true;
        }   
    } else { // Assume Thread::signal_wait timed out
    
#ifdef MULTI_TOUCH_TECHNIQUE_1
        EasyGUIDebugPrintWithCounter("*** Timer event or timeout ***", 125, 405);
#else
        // Timer used only with MULTI_TOUCH_TECHNIQUE_1
        EasyGUIDebugPrintWithCounter("*** Timeout ***", 125, 405);
#endif // MULTI_TOUCH_TECHNIQUE_1

#if defined MULTI_TOUCH_TECHNIQUE_3
        gotAtLeastOneTimeout = true;
#endif
        
        // While sending a method, do nothing else - otherwise method download is ridiculously slow
        //                         ***************
        if(!sendingMethod) {
            PollGC(stl, ethernetThread);
//        }
// Include everything else in this if - *really* do nothing else

            if(GCIsRunning()) {
    
                // While the GC is running, all pages below need to be continuously updated -
                // but do not force them to (re)display their data if it has not changed
                if(currentPage == GuiStruct_RunningColumnPage_25) {
                    DisplayRunningColumnPageData(false, false);
                } else if (currentPage == GuiStruct_RunningGasPage_28) {
                    DisplayRunningGasPageData(false, false);
                } else if (currentPage == GuiStruct_RunningInjectorProfilePage_Def) {
                    DisplayRunningInjectorProfilePageData(false, false);
                } else if (currentPage == GuiStruct_RunningPage1_7) {
                    DisplayRunningPageData(false, false);
                }
                            
            } else if(realGCIsRunning) {
                
                // The GC has stopped running, but our "the GC is running" flag is still set - 
                // either the run has ended normally, it has been aborted, or the GC has faulted
                // (although now we should be dealing with the latter situation above,
                // when we detect that the GC has faulted)
                
                if(!GCHasFaulted()) {
                    
                    if(runWasAborted) {
    
                        DisplayRunAbortedPage();
                        
                        // Prepare for next run...
                        runWasAborted = false;
    
                    } else {
    
                        // The run has ended normally - give the progress bar and method profiles a chance to show this to the user
                        if(currentPage == GuiStruct_RunningColumnPage_25) {
                            DisplayRunningColumnPageData(false, true);
                        } else if (currentPage == GuiStruct_RunningGasPage_28) {
                            DisplayRunningGasPageData(false, true);
                        } else if (currentPage == GuiStruct_RunningInjectorProfilePage_Def) {
                            DisplayRunningInjectorProfilePageData(false, true);
                        } else if (currentPage == GuiStruct_RunningPage1_7) {
                            DisplayRunningPageData(false, true);
                        }
                        
                        // Let the user briefly see the above before we display "Run Completed"
                        Thread::wait(500); // 0.5 second
    
                        DisplayRunCompletePage();
                        
                        // Want to display "Run Complete" for 5 seconds, then the Home page.
                        // Maybe this is all that is needed...
                        // Thread::wait(5000);
                        // DisplayHomePageData(true);


//#define BUG_4_ALLOW_DEBUG_PRINTS_HERE
#ifdef BUG_4_ALLOW_DEBUG_PRINTS_HERE
                        char bug4[100];
                        sprintf(bug4, "+++ realGCIsRunning %d +++", realGCIsRunning);
                        SpecialDebugPrint(bug4, 150, 440);
                        sprintf(bug4, "+++ Thread %X +++", osThreadGetId());
                        SpecialDebugPrint(bug4, 150, 460);
#undef BUG_4_ALLOW_DEBUG_PRINTS_HERE
#endif // BUG_4_ALLOW_DEBUG_PRINTS_HERE
                    }
                }
                // 'else' the GC has faulted while the run was in progress - we will display the 'GCInFaultStatePage' anyway
                // (see the top of this function)
                
                // Now tidy up...
                ClearGCIsRunning();
                
#ifdef SERVICE_INTERVALS_ACTIVE
                ServiceInterval::TellAllServiceIntervalsInstrumentHasCycled();
                // Some components need servicing based on how many cycles the instrument has performed
                // (others are time-based, i.e. every twelve months)
                if(ServiceInterval::AtLeastOneServiceIntervalHasExpired()) {
                    DisplayServicingRequiredPage();
                }
#endif // SERVICE_INTERVALS_ACTIVE
            }
        } // if (!sendingMethod)
    }
#else // USE_THREAD_WAIT
    wait_ms(waitTimeMs); // Let other things happen
#endif // USE_THREAD_WAIT

}

/*
    Along with TouchCallBack (main.cpp), this is one of the most important functions in this application.
    ****************************************************************************************************
    
    Once called, this function never returns - it enters a loop in which it sets up the Ethernet interface,
    which in turn enters an inner loop calling HandleUserInterfaceAndEthernet (see above) repeatedly. 
    This inner loop exits only if the Ethernet parameters have changed, 
    and we have to set up the Ethernet interface again.
*/
void GetGCStatusLoop::MainLoopWithEthernet(DMBoard* board)
{
    // Need to do this once only - it is nothing to do with the Ethernet interface
    SimplifiedTouchListener* stl = SimplifiedTouchListener::GetInstance(osThreadGetId(), board->touchPanel());
    
    Thread* ethernetThread;
    EthernetInterface eth;
    TCPSocketServer server;
    
    DisplayEthernetConnectionPage();
    
    EasyGUIDebugPrintWithCounter("Ethernet -2", 100, 450);
//#define ALLOW_DEBUG_PRINTS_HERE
#ifdef ALLOW_DEBUG_PRINTS_HERE
    char buff[300];
    sprintf(buff, "Ethernet -2 - stl %X", stl);
    SpecialDebugPrint(buff, 100, 400);
#endif // ALLOW_DEBUG_PRINTS_HERE
    
    NetworkParameters *networkParameters = NetworkParameters::GetInstance(usbDevice, usbHostGC);
    if(networkParameters != NULL) {
        // We are assuming here that the NetworkParameters instance will have read its values
        // from QSPI memory
        ethernetPort = networkParameters->GetPortNumber();
        networkParameters->GetIPAddressAsString(ethernetIP);
        networkParameters->GetSubnetMaskAsString(ethernetMask);
        networkParameters->GetGatewayAddressAsString(ethernetGateway);
        useDHCPForEthernet = networkParameters->GetUseDHCP();
//#define WANT_ETHERNET_DEBUG_2
#ifdef WANT_ETHERNET_DEBUG_2
        char dbg[300];
        sprintf(dbg, "networkParameters IP: \"%s\", mask: \"%s\", gateway: \"%s\"", ethernetIP, ethernetMask, ethernetGateway);
        SpecialDebugPrint(dbg, 100, 20);
#endif // WANT_ETHERNET_DEBUG_2
    }
            
    EasyGUIDebugPrintWithCounter("Ethernet -1", 100, 450);

//#define WANT_ETHERNET_DEBUG
    if(useDHCPForEthernet) {
        eth.init(); // With no args, init function uses DHCP

#ifdef WANT_ETHERNET_DEBUG
        char dbg[300];
        sprintf(dbg, "DHCP Ethernet IP: \"%s\", mask: \"%s\", gateway: \"%s\"", eth.getIPAddress(), eth.getNetworkMask(), eth.getGateway());
        SpecialDebugPrint(dbg, 100, 20);
#endif // WANT_ETHERNET_DEBUG
        
    } else {

#ifdef WANT_ETHERNET_DEBUG
        char dbg[300];
        sprintf(dbg, "Ethernet IP: \"%s\", mask: \"%s\", gateway: \"%s\"", ethernetIP, ethernetMask, ethernetGateway);
        SpecialDebugPrint(dbg, 100, 20);
#undef WANT_ETHERNET_DEBUG
#endif // WANT_ETHERNET_DEBUG
    
        eth.init(ethernetIP, ethernetMask, ethernetGateway); // Use addresses from our member variables
    }
    
    EasyGUIDebugPrintWithCounter("Ethernet -1 A", 100, 450);
    
    eth.connect();
    
    EasyGUIDebugPrintWithCounter("Ethernet -1 B", 100, 450);
    
    server.bind(ethernetPort);
    server.listen();

    EasyGUIDebugPrintWithCounter("Ethernet 0", 100, 450);
    
    // Set up Ethernet Handler, and start it in its own thread
    EthernetHandler::SetMainThreadId(osThreadGetId());
    EthernetHandler::SetEthernetServer(&server);
    EthernetHandler::SetUsbGC(usbDevice, usbHostGC);
#define ETHERNET_THREAD_RAISE_PRIORITY
#ifdef ETHERNET_THREAD_RAISE_PRIORITY
    ethernetThread = new Thread(EthernetHandler::HandlerFunction, NULL, osPriorityHigh);
#undef ETHERNET_THREAD_RAISE_PRIORITY
#else // Give the Ethernet thread normal priority
    ethernetThread = new Thread(EthernetHandler::HandlerFunction);
#endif
    
    restartRequired = false;

    EasyGUIDebugPrintWithCounter("Ethernet 1", 100, 450);

    // We are no longer trying to establish the Ethernet connection
#ifdef USING_BACKGROUND_BITMAP
    DrawBackgroundBitmap(); 
#else
    GuiLib_Clear();
#endif

    if((qspiBitmaps != NULL) && (qspiBitmaps->AllBitmapsLoaded())) {
        currentPage = GuiStruct_HomePage_1;
        DisplayCurrentPageData(true);
    } else {
        currentPage = GuiStruct_FailedToFindBitmapsPage_0;
        DisplayFailedToFindBitmapsPage();
    }
        
//#define WANT_ETHERNET_DEBUG_3
    if(useDHCPForEthernet) {
#ifdef WANT_ETHERNET_DEBUG_3
        char dbg[100];
        sprintf(dbg, "DHCP Ethernet IP: \"%s\", mask: \"%s\", gateway: \"%s\"", eth.getIPAddress(), eth.getNetworkMask(), eth.getGateway());
        SpecialDebugPrint(dbg, 100, 20);
#endif // WANT_ETHERNET_DEBUG_3

        // Let the user see (in the Network Parameters page) the actual IP address, etc, we are using
        if(networkParameters != NULL) {
            networkParameters->SetIPAddressFromString(eth.getIPAddress());
            networkParameters->SetSubnetMaskFromString(eth.getNetworkMask());
            networkParameters->SetGatewayAddressFromString(eth.getGateway());
        }
#ifdef WANT_ETHERNET_DEBUG_3
    } else {
        char dbg[600];
        int dbg1;
        char dbg2[100];
        char dbg3[100];
        char dbg4[100];
        int dbg5;

        dbg1 = networkParameters->GetPortNumber();
        networkParameters->GetIPAddressAsString(dbg2);
        networkParameters->GetSubnetMaskAsString(dbg3);
        networkParameters->GetGatewayAddressAsString(dbg4);
        dbg5 = networkParameters->GetUseDHCP();
        
        sprintf(dbg, "Non-DHCP : %d, IP: \"%s\", mask: \"%s\", gateway: \"%s\"", dbg1, dbg2, dbg3, dbg4);
        SpecialDebugPrint(dbg, 100, 20);
    }
#else
    }
#endif

    if(networkParameters != NULL) {
        networkParameters->SaveRealValues(&eth);
    }
    
    // We will need to restart (i.e. reboot) if the Ethernet parameters have changed - 
    // if you try and call the EthernetInterface init method again (which is the obvious way 
    // of doing this) it crashes
    while (!restartRequired) 
    {
        HandleUserInterfaceAndEthernet(stl, ethernetThread);
    }
    
    EasyGUIDebugPrintWithCounter("Ethernet 2", 100, 400);

    eth.disconnect();
    ethernetThread->terminate();
    delete ethernetThread;
    
    EasyGUIDebugPrintWithCounter("Ethernet 3", 100, 400);
    
    reboot(); // In main.cpp
}