Repository for import to local machine

Dependencies:   DMBasicGUI DMSupport

GetGCStatusLoop.cpp

Committer:
jmitc91516
Date:
2016-01-13
Revision:
0:47c880c1463d
Child:
1:a5258871b33d

File content as of revision 0:47c880c1463d:

#include "mbed.h"
#include "DMBoard.h"
#include <string.h>
#include "EthernetInterface.h"

#include "GetGCStatusLoop.h"
#include "GCStateAndFaultCodes.h"

#include "GuiLib.h"

extern void EasyGUIDebugPrint(char *stuffToPrint, short X, short Y);
extern GuiConst_INTCOLOR SixteenBitColorValue(GuiConst_INT8U red, GuiConst_INT8U green, GuiConst_INT8U blue);
extern void DrawRunButton(bool enabled);

// 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)
GetGCStatusLoop * GetGCStatusLoop::theGetGCStatusLoop = NULL;

const int GetGCStatusLoop::waitTimeMs = 1000;
#define USE_THREAD_WAIT

// TODO: Make these values dynamic somehow - currently will work on only one system (obviously)
const int GetGCStatusLoop::ethernetPort = 3456;
const char* GetGCStatusLoop::ethernetIP = "192.168.2.2";
const char* GetGCStatusLoop::ethernetMask = "255.255.255.0";
const char* GetGCStatusLoop::ethernetGateway = "0.0.0.0";

GCStateAndFaultCodes gcStateAndFaultCodes;


// Singleton class - return the one and only instance, first creating it if necessary
GetGCStatusLoop *GetGCStatusLoop::GetInstance(USBDeviceConnected* newUsbDevice, USBHostGC* newUsbHostGC)
{
    if (theGetGCStatusLoop == NULL) {
        theGetGCStatusLoop = new GetGCStatusLoop(newUsbDevice, newUsbHostGC);
    }
    return theGetGCStatusLoop;
}

// Singleton class - private constructor
GetGCStatusLoop::GetGCStatusLoop(USBDeviceConnected* newUsbDevice, USBHostGC* newUsbHostGC)
{
    usbDevice = newUsbDevice;
    usbHostGC = newUsbHostGC;
    
    currentPage = 9999; // Impossible value
    
    pageJustChanged = false;
    displayingData = false;
    
    gcInStandbyMode = false;

    homePageGCComponentStatusColorAreas = NULL;
    singleGCComponentPageStatusColorAreas = NULL;
}
    
GetGCStatusLoop::~GetGCStatusLoop()
{
}

GuiConst_INT16U GetGCStatusLoop::GetCurrentPage(void)
{
    return currentPage;
}

void GetGCStatusLoop::SetCurrentPage(GuiConst_INT16U newCurrentPage)
{
    if(currentPage != newCurrentPage) {
        currentPage = newCurrentPage;
        
        pageJustChanged = true; // Try this - can it prevent crashes on updating?
        
        // Stop the status rectangles flashing when we display these pages/structures
        if((currentPage != GuiStruct_HomePage_1) &&
           (currentPage != GuiStruct_ColumnPage1_2) &&
           (currentPage != GuiStruct_InjectorPage1_3) &&
           (currentPage != GuiStruct_DetectorPage1_4) &&
           (currentPage != GuiStruct_GasPage1_6)) {
            
            DisplayCurrentPageData(true);
        }
    }
}

void GetGCStatusLoop::SetHomePageGCComponentStatusColorAreas(HomePageGCComponentStatusColorAreas* newColorAreas)
{
    homePageGCComponentStatusColorAreas = newColorAreas;
    
    UpdateHomePageGCComponentStatusColorAreas();
}

void GetGCStatusLoop::SetSingleGCComponentPageStatusColorAreas(SingleGCComponentPageStatusColorAreas* newColorAreas)
{
    singleGCComponentPageStatusColorAreas = newColorAreas;
    
    UpdateSingleGCComponentPageStatusColorArea(COLUMN);
    UpdateSingleGCComponentPageStatusColorArea(INJECTOR);
    UpdateSingleGCComponentPageStatusColorArea(DETECTOR);
    UpdateSingleGCComponentPageStatusColorArea(GAS);

}


void GetGCStatusLoop::DisplayText(char *text, short X, short Y)
{
    const GuiConst_INT16U fontNo = GuiFont_Helv1;
    
    GuiLib_DrawStr(
        X,                      //GuiConst_INT16S X,
        Y,                      //GuiConst_INT16S Y,
        fontNo,                 //GuiConst_INT16U FontNo,
        text,                   //GuiConst_TEXT PrefixLocate *String,
        GuiLib_ALIGN_LEFT,      //GuiConst_INT8U Alignment, 
        GuiLib_PS_ON,           //GuiConst_INT8U PsWriting,
        GuiLib_TRANSPARENT_OFF, //GuiConst_INT8U Transparent,
        GuiLib_UNDERLINE_OFF,   //GuiConst_INT8U Underlining,
        0,                      //GuiConst_INT16S BackBoxSizeX,
        0,                      //GuiConst_INT16S BackBoxSizeY1,
        0,                      //GuiConst_INT16S BackBoxSizeY2,
        GuiLib_BBP_NONE,        //GuiConst_INT8U BackBorderPixels,
        0,                      //GuiConst_INTCOLOR ForeColor,
        0xFFFF                  //GuiConst_INTCOLOR BackColor
    ); 
}

void GetGCStatusLoop::SetGCDeviceReport(char *cmd, char *response)
{
    // 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);
}
    
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;
}

int GetGCStatusLoop::GetGCStatus(void)
{
    return GetGCStatusOrFaultCode("QSTA");
}

int GetGCStatusLoop::GetGCFaultCode(void)
{
    return GetGCStatusOrFaultCode("QFLT");
}

void GetGCStatusLoop::GetGCStateAsInfoString(int gcStateCode, int gcFaultCode, char *statusString)
{
    char buff[100];

    if(gcStateCode == 99) {

        // GC is in fault state

        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);
        }
    }
}

bool GetGCStatusLoop::GCHasFaulted(char* statusString)
{
    bool gcHasFaulted = false;   
    statusString[0] = '\0';
    
    int gcStatus = GetGCStatus();
    if(gcStatus == -1) { // Got "EPKT" as response from GC
        strcpy(statusString, "Failed to get status");
        gcHasFaulted = true;
    } else {
        int gcFaultCode = 0;
        if(gcStatus == 99) {
            gcFaultCode = GetGCFaultCode();
            
            if(gcFaultCode != 0) {
                gcHasFaulted = true;
            }
        }
    
        GetGCStateAsInfoString(gcStatus, gcFaultCode, statusString);
    }
    
    return gcHasFaulted;
}

void GetGCStatusLoop::GetComponentTemperature(char *cmd, char *temp)
{
    char response[50];
    SetGCDeviceReport(cmd, response);
    
    // We expect a response like this: "Dxxx1234" - temp in units of 0.1 deg
    temp[0] = 'T';
    temp[1] = 'e';
    temp[2] = 'm';
    temp[3] = 'p';
    temp[4] = ':';
    temp[5] = ' ';
    
    // But check for "EPKT" first
    if(response[0] == 'E') {
        temp[6]  = '*';
        temp[7]  = '*';
        temp[8]  = ' ';
        temp[9]  = 'E';
        temp[10] = 'r';
        temp[11] = 'r';
        temp[12] = 'o';
        temp[13] = 'r';
        temp[14] = ' ';
        temp[15] = '*';
        temp[16] = '*';
        temp[17] = '\0';
    } else {
        temp[6] = response[4];
        temp[7] = response[5];
        temp[8] = response[6];
        temp[9] = '.';
        temp[10] = response[7];
        temp[11] = '\0';
    }
}

void GetGCStatusLoop::GetComponentTemperature(char *cmd, float *temp)
{
    char buff[100];
    GetComponentTemperature(cmd, buff);
    // Allow for "EPKT" being returned from GC - see above
    if(buff[9] == 'E') {
        *temp = -1.0f; // ** Caller must check for this **
    } else {
        sscanf(&buff[6], "%f", temp);
    }
//#define DEBUG_HERE
#ifdef DEBUG_HERE
    char dbg[100];
    sprintf(dbg, "GGCSL::GCT - returning : %f", *temp);
    EasyGUIDebugPrint(dbg, 100, 100);
#endif
//#undef DEBUG_HERE
}

void GetGCStatusLoop::GetColumnTemperature(char *temp)
{
    GetComponentTemperature("QCOL", temp);
}

void GetGCStatusLoop::GetColumnTemperature(float *temp)
{
    GetComponentTemperature("QCOL", temp);
}

void GetGCStatusLoop::GetDetectorTemperature(char *temp)
{
    GetComponentTemperature("QDET", temp);
}

void GetGCStatusLoop::GetDetectorTemperature(float *temp)
{
    GetComponentTemperature("QDET", temp);
}

void GetGCStatusLoop::GetInjectorTemperature(char *temp)
{
    GetComponentTemperature("QINJ", temp);
}

void GetGCStatusLoop::GetInjectorTemperature(float *temp)
{
    GetComponentTemperature("QINJ", temp);
}

void GetGCStatusLoop::GetGasPressure(char *press)
{
    char response[50];
    SetGCDeviceReport("QPRS", response);

    // We expect a response like this: "DPRS1234" - pressure in units of 0.1 psi
    press[0] = 'P';
    press[1] = 'r';
    press[2] = 'e';
    press[3] = 's';
    press[4] = 's';
    press[5] = 'u';
    press[6] = 'r';
    press[7] = 'e';
    press[8] = ':';
    press[9] = ' ';

    // But check for "EPKT" first
    if(response[0] == 'E') {
        press[10] = '*';
        press[11] = '*';
        press[12] = ' ';
        press[13] = 'E';
        press[14] = 'r';
        press[15] = 'r';
        press[16] = 'o';
        press[17] = 'r';
        press[18] = ' ';
        press[19] = '*';
        press[20] = '*';
        press[21] = '\0';
    } else {    
        press[10] = response[4];
        press[11] = response[5];
        press[12] = response[6];
        press[13] = '.';
        press[14] = response[7];
        press[15] = '\0';
    }
}

void GetGCStatusLoop::GetGasPressure(float *press)
{
    char buff[100];
    GetGasPressure(buff);
    // Allow for "EPKT" being returned from GC - see above
    if(buff[13] == 'E') {
        *press = -1.0f; // ** Caller must check for this **
    } else {
        sscanf(&buff[10], "%f", press);
    }
//#define DEBUG_HERE
#ifdef DEBUG_HERE
    char dbg[100];
    sprintf(dbg, "GGCSL::GGP - returning : %f", *press);
    EasyGUIDebugPrint(dbg, 100, 100);
#endif
//#undef DEBUG_HERE
}

void GetGCStatusLoop::DisplayHomePageData(bool mustUpdateDisplay)
{
//    EasyGUIDebugPrint("Home Page", 100, 100);

    char buff[40];
    
    GetColumnTemperature(buff);
    if(strcmp(buff, GuiVar_columnTemperature2) != 0) {
        mustUpdateDisplay = true;
        
        strcpy(GuiVar_columnTemperature2, buff);
    }

    GetDetectorTemperature(buff);
    if(strcmp(buff, GuiVar_detectorTemperature2) != 0) {
        mustUpdateDisplay = true;
        
        strcpy(GuiVar_detectorTemperature2, buff);
    }

    GetInjectorTemperature(buff);
    if(strcmp(buff, GuiVar_injectorTemperature2) != 0) {
        mustUpdateDisplay = true;
        
        strcpy(GuiVar_injectorTemperature2, buff);
    }

    GetGasPressure(buff);
    if(strcmp(buff, GuiVar_gasPressure2) != 0) {
        mustUpdateDisplay = true;
        
        strcpy(GuiVar_gasPressure2, buff);
    }

    if(mustUpdateDisplay) {

        // 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();
        }

        // Also get the run ready state before GuiLib_Clear, for the same reason
        bool gcIsReadyToRun = (GetGCStatus() == 33);
            
        // Makes the display flicker - but omitting it means old text is not cleared from the display
//#define WANT_GUILIB_CLEAR
#ifdef WANT_GUILIB_CLEAR
        GuiLib_Clear();
#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 - so only the text flickers, not the rectangles

        // 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();
        }

        GuiLib_ShowScreen(GuiStruct_HomePage_1, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);
        
        // ...but we draw the run button on top of the structure
        DrawRunButton(gcIsReadyToRun);
    
        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, 100, 100);
#undef DEBUG_HERE
#endif
    }
}

void GetGCStatusLoop::GetColumnMaxTemperature(char *maxTemp, bool wantFullPrefix)
{
    char response[50];
    SetGCDeviceReport("QCMX", response);

    // We expect a response like this: "DCMX1234" - max temp in units of 0.1 deg
    int index = 0;
    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++] = '*';
        maxTemp[index++] = '\0';
    } else {
        maxTemp[index++] = response[4];
        maxTemp[index++] = response[5];
        maxTemp[index++] = response[6];
        maxTemp[index++] = '.';
        maxTemp[index++] = response[7];
        maxTemp[index++] = '\0';    
    }
}

void GetGCStatusLoop::GetColumnMaxTemperature(float *maxTemp)
{
    char response[50];
    SetGCDeviceReport("QCMX", response);

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

void GetGCStatusLoop::DisplayColumnPageData(bool mustUpdateDisplay)
{
//    EasyGUIDebugPrint("Column Page", 100, 100);
    // Column temperature and maximum temperature
    char buff[40];
    
    GetColumnTemperature(buff);
    if(strcmp(buff, GuiVar_columnTemperature) != 0) {
        mustUpdateDisplay = true;
        
        strcpy(GuiVar_columnTemperature, buff);
    }

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

    if(mustUpdateDisplay) {
        
        // Reduce display flickering - get the component status from the GC
        // *before* we call GuiLib_Clear()
        if(singleGCComponentPageStatusColorAreas != NULL) {
            UpdateSingleGCComponentPageStatusColorArea(COLUMN);
        }
        
        GuiLib_Clear(); // Makes the display flicker - but omitting it means old text is not cleared from the display

        // 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);
        }

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

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

void GetGCStatusLoop::GetInjectionMode(char *mode, bool wantFullPrefix)
{
    char response[50];
    SetGCDeviceReport("QIMD", response);

    // We expect a response like this: "DIMD0000" for split, "DIMD0001" for splitless
    int index = 0;
    if(wantFullPrefix) {
        mode[index++]  = 'I';
        mode[index++]  = 'n';
        mode[index++]  = 'j';
        mode[index++]  = 'e';
        mode[index++]  = 'c';
        mode[index++]  = 't';
        mode[index++]  = 'i';
        mode[index++]  = 'o';
        mode[index++]  = 'n';
        mode[index++]  = ' ';
        mode[index++]  = 'm';
    } else {
        mode[index++]  = 'M';
    }
    mode[index++]  = 'o';
    mode[index++]  = 'd';
    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 {
        mode[index++]  = 'S';
        mode[index++]  = 'p';
        mode[index++]  = 'l';
        mode[index++]  = 'i';
        mode[index++]  = 't';
        if(response[7] == '0') {
            mode[index++] = '\0';
        } else {
            mode[index++] = 'l';
            mode[index++] = 'e';
            mode[index++] = 's';
            mode[index++] = 's';
            mode[index++] = '\0';
        }    
    }
}

void GetGCStatusLoop::DisplayInjectorPageData(bool mustUpdateDisplay)
{
//    EasyGUIDebugPrint("Injector Page", 100, 100);
    // Injector temperature and mode
    char buff[40];
    
    GetInjectorTemperature(buff);
    if(strcmp(buff, GuiVar_injectorTemperature) != 0) {
        mustUpdateDisplay = true;
        
        strcpy(GuiVar_injectorTemperature, buff);
    }

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

    if(mustUpdateDisplay) {

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

        // 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);
        }

        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, 100, 100);
#undef DEBUG_HERE
#endif
    }
}

void GetGCStatusLoop::GetDetectorType(char *type, bool wantFullPrefix)
{
    char response[50];
    SetGCDeviceReport("QDTY", response);

    // We expect a response like this: "DDTY0000" for FID, "DIMD0001" for TCD,
    // "DIMD0002" for ECD, "DIMD0003" for PID, "DIMD0004" for PDID, "DIMD0099" 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;
        }
    }
}

void GetGCStatusLoop::DisplayDetectorPageData(bool mustUpdateDisplay)
{
//    EasyGUIDebugPrint("Detector Page", 100, 100);
    // Detector temperature and type
    char buff[40];
    
    GetDetectorTemperature(buff);
    if(strcmp(buff, GuiVar_detectorTemperature) != 0) {
        mustUpdateDisplay = true;
        
        strcpy(GuiVar_detectorTemperature, buff);
    }

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

    if(mustUpdateDisplay) {

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

        // 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);
        }

        GuiLib_ShowScreen(GuiStruct_DetectorPage1_4, 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, 100, 100);
#undef DEBUG_HERE
#endif
    }
}

bool GetGCStatusLoop::GetGasControlMode(char *mode, bool wantFullPrefix)
{
    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
    int index = 0;
    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;
}

void GetGCStatusLoop::DisplayGasPageData(bool mustUpdateDisplay)
{
    //EasyGUIDebugPrint("Gas Page", 100, 100);
    // Gas pressure and control mode
    char buff[60];
    
    GetGasPressure(buff);
    if(strcmp(buff, GuiVar_gasPressure) != 0) {
        mustUpdateDisplay = true;
        
        strcpy(GuiVar_gasPressure, buff);
    }

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

    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
        GuiLib_Clear();

        // 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);
        }

        GuiLib_ShowScreen(GuiStruct_GasPage1_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, 100, 100);
#undef DEBUG_HERE
#endif
    }
}

void GetGCStatusLoop::DisplayRunningPageData(bool mustUpdateDisplay)
{
    EasyGUIDebugPrint("Running Page", 100, 100);
}

void GetGCStatusLoop::DisplayRunningSettingsPageData(bool mustUpdateDisplay)
{
    EasyGUIDebugPrint("Running Settings Page", 100, 100);
}

void GetGCStatusLoop::GetSoftwareVersion(char *version)
{
    char response[50];
    SetGCDeviceReport("QWHO", response);

    // We expect a response like this: "DWHO0320" -> version 3.20
    version[0]  = 'V';
    version[1]  = 'e';
    version[2]  = 'r';
    version[3]  = 's';
    version[4]  = 'i';
    version[5]  = 'o';
    version[6]  = 'n';
    version[7]  = ':';
    version[8]  = ' ';
    version[9]  = response[4];
    version[10] = response[5];
    version[11] = '.';
    version[12] = response[6];
    version[13] = response[7];
    version[14] = '\0';
}

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
    time[0]  = 'R';
    time[1]  = 'u';
    time[2]  = 'n';
    time[3]  = ' ';
    time[4]  = 't';
    time[5]  = 'i';
    time[6]  = 'm';
    time[7]  = 'e';
    time[8]  = ':';
    time[9]  = ' ';
    time[10] = response[4];
    time[11] = response[5];
    time[12] = response[6];
    time[13] = '.';
    time[14] = response[7];
    time[15] = '\0';
}

void GetGCStatusLoop::DisplaySettingsPageData(bool mustUpdateDisplay)
{
    //EasyGUIDebugPrint("Settings Page", 100, 100);
    // Various settings
    char buff[60];

    GetSoftwareVersion(GuiVar_gcSoftwareVersion);
    // Assume software version cannot change while we are running
    
    GetGasControlMode(buff, true);
    if(strcmp(buff, GuiVar_gasControlMode) != 0) {
        mustUpdateDisplay = true;
        
        strcpy(GuiVar_gasControlMode, buff);
    }
    
    GetDetectorType(buff, true);
    if(strcmp(buff, GuiVar_detectorType) != 0) {
        mustUpdateDisplay = true;
        
        strcpy(GuiVar_detectorType, buff);
    }
    
    GetColumnMaxTemperature(buff, true);
    if(strcmp(buff, GuiVar_columnMaxTemp) != 0) {
        mustUpdateDisplay = true;
        
        strcpy(GuiVar_columnMaxTemp, buff);
    }
    
    GetInjectionMode(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);
    }

    if(mustUpdateDisplay) {

        // Makes the display flicker - but omitting it means old text is not cleared from the display
        GuiLib_Clear();

        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, 100, 100);
#undef DEBUG_HERE
#endif
    }
}

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;
    
    switch(currentPage) {
        case GuiStruct_HomePage_1:
            DisplayHomePageData(mustUpdateDisplay);
            break;
        case GuiStruct_ColumnPage1_2:
            DisplayColumnPageData(mustUpdateDisplay);
            break;
        case GuiStruct_InjectorPage1_3:
            DisplayInjectorPageData(mustUpdateDisplay);
            break;
        case GuiStruct_DetectorPage1_4:
            DisplayDetectorPageData(mustUpdateDisplay);
            break;
        case GuiStruct_GasPage1_6:
            DisplayGasPageData(mustUpdateDisplay);
            break;
        case GuiStruct_RunningPage_7:
            DisplayRunningPageData(mustUpdateDisplay);
            break;
        case GuiStruct_RunningSettings_8:
            DisplayRunningSettingsPageData(mustUpdateDisplay);
            break;
        case GuiStruct_SettingsPage_5:
            DisplaySettingsPageData(mustUpdateDisplay);
            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');
}

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
    if(response[0] == 'E') return 99; // Faulted

    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, 100, 100);
#endif
//#undef DEBUG_HERE
    return retval;
}

GCComponentStatus GetGCStatusLoop::GetComponentStatus(GCComponent component)
{
    // Certain values for the overall instrument status override the status(es) of the individual components
    switch (GetInstrumentStatus()) {
        case 33: // Instrument is ready
            return READY;
        case 99: // Instrument has faulted somehow
            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:
            GetGasPressure(&pressure);
            if(pressure < 0.0f) {// Got "EPKT" response
                return FAULTED;
            }
            if(GetGasControlMode(buff, false) == false) {
                // Got "EPKT"
                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, 100, 100);
            break;
    }

    return FAULTED;
}

bool GetGCStatusLoop::GCIsInStandbyMode(void)
{
    char response[50];
    SetGCDeviceReport("QDIS", response);
    
    // We expect a response of the form "DHTS000n", 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", 100, 100);
    } else {
        EasyGUIDebugPrint("GGCSL::GCIISM - returning false", 100, 100);
    }
#endif
//#undef DEBUG_HERE
    return retval;
}

void GetGCStatusLoop::ExitedGCStandbyMode(void)
{
    gcInStandbyMode = false;
}

void GetGCStatusLoop::DisplayStandbyModePage(void)
{
    GuiLib_Clear();

    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, 100, 100);
#undef DEBUG_HERE
#endif
        
    SetCurrentPage(GuiStruct_GasSaver_9);
}

void GetGCStatusLoop::DisplayGCInFaultStatePage(void)
{
    GuiLib_Clear();

    // Display red background to the error message
    GuiLib_FillBox(10, 10, 790, 420, SixteenBitColorValue(0xFF, 0, 0)); // red
        
    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, 100, 100);
#undef DEBUG_HERE
#endif
        
    SetCurrentPage(GuiStruct_GCInFaultStatePage_11);
}

void GetGCStatusLoop::UpdateHomePageGCComponentStatusColorAreas(void)
{
    if(homePageGCComponentStatusColorAreas != NULL) {
        homePageGCComponentStatusColorAreas->SetGCComponentStatus(COLUMN, GetComponentStatus(COLUMN));
        homePageGCComponentStatusColorAreas->SetGCComponentStatus(INJECTOR, GetComponentStatus(INJECTOR));
        homePageGCComponentStatusColorAreas->SetGCComponentStatus(DETECTOR, GetComponentStatus(DETECTOR));
        homePageGCComponentStatusColorAreas->SetGCComponentStatus(GAS, GetComponentStatus(GAS));
    }
}

void GetGCStatusLoop::UpdateSingleGCComponentPageStatusColorArea(GCComponent component)
{
    if(singleGCComponentPageStatusColorAreas != NULL) {
        singleGCComponentPageStatusColorAreas->SetGCComponentStatus(component, GetComponentStatus(component));
    }
}

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
    GetColumnTemperature(GuiVar_columnTemperature2);
    GetDetectorTemperature(GuiVar_detectorTemperature2);
    GetInjectorTemperature(GuiVar_injectorTemperature2);
    GetGasPressure(GuiVar_gasPressure2);
#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 expect.
        
    // Column page
    GetColumnTemperature(GuiVar_columnTemperature);
    GetColumnMaxTemperature(GuiVar_columnMaxTemp2, false);
       
    // Injector page
    GetInjectorTemperature(GuiVar_injectorTemperature);
    GetInjectionMode(GuiVar_injectionMode2, false);
    
    // Detector page
    GetDetectorTemperature(GuiVar_detectorTemperature);
    GetDetectorType(GuiVar_detectorType2, false);    
    
    // Gas page
    GetGasPressure(GuiVar_gasPressure);
    GetGasControlMode(GuiVar_gasControlMode2, true);
    
    // Settings page
    GetSoftwareVersion(GuiVar_gcSoftwareVersion);    
    GetRunTime(GuiVar_runTime);    
    GetInjectionMode(GuiVar_injectionMode, true);
    GetColumnMaxTemperature(GuiVar_columnMaxTemp, true);
    GetDetectorType(GuiVar_detectorType, true);
    GetGasControlMode(GuiVar_gasControlMode, true);
}


void GetGCStatusLoop::MainLoop(void)
{
#ifdef PAGE_DEBUG
    char dbg[100];
    char response[50];
#endif

    char statusString[100];
    
    // We will now sit in this loop until the end of time, or the user powers off, whichever happens sooner...
    while(true) {

#ifdef PAGE_DEBUG
        sprintf(dbg, "Current page is: %d                   ", GetCurrentPage());
        EasyGUIDebugPrint(dbg, 20, 80);
        
        usbHostGC->SetDeviceReport(usbDevice, "QCOL", response);
        sprintf(dbg, "QCOL response was: %s                   ", response);
        EasyGUIDebugPrint(dbg, 20, 100);
#endif
        if(GCIsInStandbyMode()) {
            if(!gcInStandbyMode) {
                gcInStandbyMode = true;
                
                DisplayStandbyModePage();
            }
        }
        
        if(GCHasFaulted(statusString)) {
            if((currentPage != GuiStruct_GCInFaultStatePage_11)
            || (strcmp(GuiVar_gcState, statusString) != 0)) {
                strcpy(GuiVar_gcState, statusString);
                
                DisplayGCInFaultStatePage();
            }
        }
        
        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);
        }
        
#ifdef USE_THREAD_WAIT
        Thread::wait(waitTimeMs); // Let other things happen
#else
        wait_ms(waitTimeMs); // Let other things happen
#endif
    }
}


void GetGCStatusLoop::MainLoopWithEthernet(void)
{
#define WANT_ETHERNET
#ifdef WANT_ETHERNET
    EthernetInterface eth;
    eth.init(ethernetIP, ethernetMask, ethernetGateway);
    eth.connect();

//    EasyGUIDebugPrint("Ethernet 0", 20, 80);
    
    TCPSocketServer server;
    server.bind(ethernetPort);
    server.listen();

    char buffer[256];

//    EasyGUIDebugPrint("Ethernet 1", 20, 80);
#endif //WANT_ETHERNET

    char statusString[100];
    
    // We will now sit in this loop until the end of time, or the user powers off, whichever happens sooner...
    while(true) {

#ifdef WANT_ETHERNET
        TCPSocketConnection client;

//        EasyGUIDebugPrint("Ethernet 2", 20, 80);
        
        // Look for a client, but do not hang if there isn't one
        server.set_blocking(false); // Timeout after (1.5)s
        bool clientFound = (server.accept(client) == 0);
//        EasyGUIDebugPrint("Ethernet 3", 20, 80);
        if(clientFound) {
//            EasyGUIDebugPrint("Found Ethernet client        ", 20, 100);
            
            client.set_blocking(false, 1500); // Timeout after (1.5)s

            // Now look for messages from the Ethernet client - as long as they keep coming,
            // pass them to the GC, and pass its responses back to the client

            while (true) 
            {
                char response[GC_MESSAGE_LENGTH + 2];

                // Note that since this is non-blocking, it is effectively 'polling' the Ethernet connection
                int n = client.receive(buffer, sizeof(buffer));
                if (n <= 0) break;
                
                buffer[n] = '\0';

                SetGCDeviceReport(buffer, response);
                                
                // Echo received message back to client
                client.send_all(response, strlen(response));
                if (n <= 0) break;
            }
                                    
            client.close();
        } else {
//            EasyGUIDebugPrint("Not found Ethernet client", 20, 100);
        }
#endif // WANT_ETHERNET

        if(GCIsInStandbyMode()) {
            if(!gcInStandbyMode) {
                gcInStandbyMode = true;
                
                DisplayStandbyModePage();
            }
        }
        
        if(GCHasFaulted(statusString)) {
            if((currentPage != GuiStruct_GCInFaultStatePage_11)
            || (strcmp(GuiVar_gcState, statusString) != 0)) {
                strcpy(GuiVar_gcState, statusString);
                
                DisplayGCInFaultStatePage();
            }
        }
        
        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);
        }
        
#ifdef USE_THREAD_WAIT
        Thread::wait(waitTimeMs); // Let other things happen
#else
        wait_ms(waitTimeMs); // Let other things happen
#endif
    }
}