Repository for import to local machine

Dependencies:   DMBasicGUI DMSupport

ColumnDHAutoCalibrationPageHandler.cpp

Committer:
jmitc91516
Date:
2017-07-31
Revision:
8:26e49e6955bd
Parent:
1:a5258871b33d

File content as of revision 8:26e49e6955bd:

#include "ColumnDHAutoCalibrationPageHandler.h"
#include "EasyGUITouchAreaIndices.h"
#include "GetGCStatusLoop.h"
#include "USBHostGCUtilities.h"

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


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

/*
    Allows us to tell the world not to unlock the door actuators while we are calibrating.
    
    Defined in main.cpp
*/
extern void CanUnlockDoorActuators(bool value);

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


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


/*                      
    Note that this class is a singleton - we do not need or want there to be more than one instance of it
    (we do not want multiple values for the calibration point selection, etc, and nor will we show 
    more than one easyGUI 'ColumnDHAutoCalibrationPage' to the user at the same time).
*/
ColumnDHAutoCalibrationPageHandler * ColumnDHAutoCalibrationPageHandler::theColumnDHAutoCalibrationPageHandlerInstance = NULL;


/*
    String saying "we do not yet know this value"
*/
const char* ColumnDHAutoCalibrationPageHandler::notAvailable = "-";
    
/*
    Singleton class - return the one and only instance, first creating it if necessary.
*/
ColumnDHAutoCalibrationPageHandler * ColumnDHAutoCalibrationPageHandler::GetInstance(USBDeviceConnected* newUsbDevice, USBHostGC* newUsbHostGC)
{
    if (theColumnDHAutoCalibrationPageHandlerInstance == NULL) {
        theColumnDHAutoCalibrationPageHandlerInstance = new ColumnDHAutoCalibrationPageHandler(newUsbDevice, newUsbHostGC);
    }
    
    return theColumnDHAutoCalibrationPageHandlerInstance;
}

/*
    Overriden version of the above, that does not take any arguments and does not create the instance 
    if it does not already exist.
    
    Provided for callers that do not have the 'usbDevice' and'usbHostGC' pointers, and just want access 
    to the instance if it exists
*/
ColumnDHAutoCalibrationPageHandler * ColumnDHAutoCalibrationPageHandler::GetInstance(void)
{
    return theColumnDHAutoCalibrationPageHandlerInstance;
}


// Singleton class - private constructor
ColumnDHAutoCalibrationPageHandler::ColumnDHAutoCalibrationPageHandler(USBDeviceConnected* newUsbDevice, USBHostGC* newUsbHostGC)
{
    usbDevice = newUsbDevice;
    usbHostGC = newUsbHostGC;
    
    strcpy(GuiVar_columnDHAutoCalibCalibrateButtonText, "Calibrate");

    for (int index = 0; index < 3; ++index) {
        calibTemperature[index] = GetColumnTemperaturePointFromGC(index + 1);
        calibResistance[index] = GetColumnResistancePointFromGC(index + 1);
    }

    currentCalibrationState = GetCalibrationState();
    previousCalibrationState = INVALID;
    SetEasyGUICalibrationState();
    ShowSetCalibrationButtonEnabledState();

    SetEasyGUIHeatState();
    SetEasyGUIOvenTemperature();
    
    stabilisationTime = GetStabilisationTimeFromGC();    
    
    actualColumnResistance = -1.0f;
    
    needToGetColumnResistance = false;
    
#ifndef USE_STABILISATION_TIMER
    gcTimeStabilisationStarted = -1.0f;
#endif

#ifdef USE_ENABLE_SET_BUTTON_FLAG 
    enableSetButton = false;
#endif
}

// Private destructor also
ColumnDHAutoCalibrationPageHandler::~ColumnDHAutoCalibrationPageHandler()
{
}

/*
    Tells the caller whether or not the specified touch index is on the easyGUI 'ColumnDHAutoCalibrationPage',
    and therefore needs to be handled by this class.
    
    Args: the touch area index in question
    
    Return code: true if the touch area is 'one of ours', false if not
*/
bool ColumnDHAutoCalibrationPageHandler::TouchAreaIsOnCalibrationPage(int touchAreaIndex)
{
    if((touchAreaIndex >= MIN_COLUMN_DH_AUTO_CALIB_TOUCHINDEX) && (touchAreaIndex <= MAX_COLUMN_DH_AUTO_CALIB_TOUCHINDEX)) {
        return true;
    }
    
    // 'else'
    return false;
}


/*
    If the specified touch area represents a key or field on the easyGUI 'ColumnDHCalibrationPage',
    this function performs whatever action is appropriate for it. Provided so that the caller
    can (in effect) say "if this is one of yours, deal with it", without needing to know 
    anything about what this class does, or how it handles the touch areas on that easyGUI page.

    Args: the touch area index in question
    
    Returns true if it dealt with the touch area (so the caller need not do anything else 
    with the value), or false if it did not deal with the touch area (implying that it 
    was not a touch area on the easyGUI 'Directly Heated Column Auto CalibrationPage', and so the caller
    must deal with it some other way).
*/
bool ColumnDHAutoCalibrationPageHandler::DealWithTouch(int touchAreaIndex)
{
    bool dealtWithTouch = false;
    
    // Don't allow the user to select a different calibration point,
    // or change any calibration parameters, while we are calibrating the current point
    switch(touchAreaIndex) {
        case COLUMN_DH_AUTO_CALIB_SELECT_POINT_1:
            if(!CalibratingNow()) {
                if(GuiVar_columnDHAutoCalibPointSelection != 0) {
                    GuiVar_columnDHAutoCalibPointSelection = 0;
                    
                    calibTemperature[GuiVar_columnDHAutoCalibPointSelection] = GetColumnTemperaturePointFromGC(GuiVar_columnDHAutoCalibPointSelection + 1);
                    SetEasyGUICalibrationPointTemperatureFromInternalValue(true);
                    
                    calibResistance[GuiVar_columnDHAutoCalibPointSelection] = GetColumnResistancePointFromGC(GuiVar_columnDHAutoCalibPointSelection + 1);
                    SetEasyGUICalibrationPointResistanceFromInternalValue();
                    
#ifdef USE_ENABLE_SET_BUTTON_FLAG 
                    enableSetButton = false;
#endif                    
                    UpdateEasyGUIPage();
                }
            }
            dealtWithTouch = true;
            break;

        case COLUMN_DH_AUTO_CALIB_SELECT_POINT_2:
            if(!CalibratingNow()) {
                if(GuiVar_columnDHAutoCalibPointSelection != 1) {
                    GuiVar_columnDHAutoCalibPointSelection = 1;
                    
                    calibTemperature[GuiVar_columnDHAutoCalibPointSelection] = GetColumnTemperaturePointFromGC(GuiVar_columnDHAutoCalibPointSelection + 1);
                    SetEasyGUICalibrationPointTemperatureFromInternalValue(true);
                    
                    calibResistance[GuiVar_columnDHAutoCalibPointSelection] = GetColumnResistancePointFromGC(GuiVar_columnDHAutoCalibPointSelection + 1);
                    SetEasyGUICalibrationPointResistanceFromInternalValue();
                    
#ifdef USE_ENABLE_SET_BUTTON_FLAG 
                    enableSetButton = false;
#endif
                    UpdateEasyGUIPage();
                }
            }
            dealtWithTouch = true;
            break;

        case COLUMN_DH_AUTO_CALIB_SELECT_POINT_3:
            if(!CalibratingNow()) {
                if(GuiVar_columnDHAutoCalibPointSelection != 2) {
                    GuiVar_columnDHAutoCalibPointSelection = 2;
                    
                    calibTemperature[GuiVar_columnDHAutoCalibPointSelection] = GetColumnTemperaturePointFromGC(GuiVar_columnDHAutoCalibPointSelection + 1);
                    SetEasyGUICalibrationPointTemperatureFromInternalValue(true);
                    
                    calibResistance[GuiVar_columnDHAutoCalibPointSelection] = GetColumnResistancePointFromGC(GuiVar_columnDHAutoCalibPointSelection + 1);
                    SetEasyGUICalibrationPointResistanceFromInternalValue();
                    
#ifdef USE_ENABLE_SET_BUTTON_FLAG 
                    enableSetButton = false;
#endif
                    UpdateEasyGUIPage();
                }
            }
            dealtWithTouch = true;
            break;
            
        case COLUMN_DH_AUTO_CALIB_INC_STABILISATION_TIME:
            if(!CalibratingNow()) {
                ++stabilisationTime;
                SetEasyGUIStabilisationTimeFromInternalValue();
                UpdateEasyGUIPage();
            }
            dealtWithTouch = true;
            break;

        case COLUMN_DH_AUTO_CALIB_DEC_STABILISATION_TIME:
            if(!CalibratingNow()) {
                if(stabilisationTime > 0) {
                    --stabilisationTime;
                    SetEasyGUIStabilisationTimeFromInternalValue();
                    UpdateEasyGUIPage();
                }
            }
            dealtWithTouch = true;
            break;

        case COLUMN_DH_AUTO_CALIB_GET_STABILISATION_TIME:        
            if(!CalibratingNow()) {
                stabilisationTime = GetStabilisationTimeFromGC();
                SetEasyGUIStabilisationTimeFromInternalValue();
                UpdateEasyGUIPage();
            }
            dealtWithTouch = true;
            break;

        case COLUMN_DH_AUTO_CALIB_SET_STABILISATION_TIME:        
            if(!CalibratingNow()) {
                SetStabilisationTimeInGC(stabilisationTime);
            }
            dealtWithTouch = true;
            break;

        case COLUMN_DH_AUTO_CALIB_INC_CALIB_TEMP:
            if(!CalibratingNow()) {
                calibTemperature[GuiVar_columnDHAutoCalibPointSelection] += 1.0f;
                SetEasyGUICalibrationPointTemperatureFromInternalValue(false);
                UpdateEasyGUIPage();
            }
            dealtWithTouch = true;
            break;
        
        case COLUMN_DH_AUTO_CALIB_DEC_CALIB_TEMP:
            if(!CalibratingNow()) {
                if(calibTemperature[GuiVar_columnDHAutoCalibPointSelection] >= 1.0f) {
                    calibTemperature[GuiVar_columnDHAutoCalibPointSelection] -= 1.0f;
                    SetEasyGUICalibrationPointTemperatureFromInternalValue(false);
                    UpdateEasyGUIPage();
                }
            }
            dealtWithTouch = true;
            break;
            
        case COLUMN_DH_AUTO_CALIB_START_CALIBRATION:
            if(CalibratingNow()) {
                // Need to stop calibrating
                char commandBuffer[30];
                ConstructStopCalibrationCommand(commandBuffer);
                if(SendCommandToGCWithDACKResponse(commandBuffer)) {
                    // Stop calibration succeeded
                    
                    currentCalibrationState = IDLE;
                    
                    needToGetColumnResistance = false;

                    strcpy(GuiVar_columnDHAutoCalibCalibrateButtonText, "Calibrate");

#ifdef USE_ENABLE_SET_BUTTON_FLAG 
                    enableSetButton = false;
#endif                
                    // Let user change pages again
                    TouchPanelPageSelector::SetPageChangeEnabled(true);
                }
            } else {
                // Need to start calibrating
                char commandBuffer[30];
                ConstructStartCalibrationCommand(commandBuffer);
                if(SendCommandToGCWithDACKResponse(commandBuffer)) {
                    // Start calibration succeeded
                    currentCalibrationState = GetCalibrationState();
                    previousCalibrationState = INVALID; // Make explicit - we have started a new calibration operation
                                                        // (do not leave STAGE_3 as the previous calibration state)
    
                    // Make sure the stabilisation time is the same as in the GC - 
                    // i.e. that it matches the value the GC is actually using
                    stabilisationTime = GetStabilisationTimeFromGC();
                    SetEasyGUIStabilisationTimeFromInternalValue();
                    
                    // Make sure we have a valid stabilisation start time,
                    // in case we somehow skip stage 1 and go straight to stage 1A
                    // (see function SetEasyGUICalibrationTimeRemaining())
                    gcTimeStabilisationStarted = GetTimeFromGC();
                    
                    strcpy(GuiVar_columnDHAutoCalibCalibrateButtonText, "Stop");
                    
                    needToGetColumnResistance = true;

#ifdef USE_ENABLE_SET_BUTTON_FLAG 
                    enableSetButton = false;
#endif
                    // Do not allow user to change pages while we are calibrating
                    TouchPanelPageSelector::SetPageChangeEnabled(false);
                }
            }
            UpdateEasyGUIPage();
            dealtWithTouch = true;
            break;
            
        case COLUMN_DH_AUTO_CALIB_SET_CURRENT_CALIBRATION:
#ifdef USE_ENABLE_SET_BUTTON_FLAG 
            if(enableSetButton) {
#else
            // 'Set' is valid during *** and after *** stage 3
            if((currentCalibrationState == STAGE_3) || (previousCalibrationState == STAGE_3)) {
#endif
                // TODO: Use the 'new' values shown to the user on the easyGUI page,
                // which we got from the GC at the end of calibration stage 3.
                // These are in:
                // GuiVar_columnDHAutoCalibTemperatureToSet for temperature
                // GuiVar_columnDHAutoCalibResistance for resistance
                // DON'T get the current values from the GC - they will have changed since 
                // the calibration process ended
                float temp;
                sscanf(GuiVar_columnDHAutoCalibTemperatureToSet, "%f", &temp);
                SetColumnTemperaturePointInGC(GuiVar_columnDHAutoCalibPointSelection + 1, temp);
                
                sscanf(GuiVar_columnDHAutoCalibResistance, "%f", &temp);
                SetColumnResistancePointInGC(GuiVar_columnDHAutoCalibPointSelection + 1, temp);

                // Update the user interface to show the new resistance value for the current calibration point
                calibResistance[GuiVar_columnDHAutoCalibPointSelection] = GetColumnResistancePointFromGC(GuiVar_columnDHAutoCalibPointSelection + 1);
                SetEasyGUICalibrationPointResistanceFromInternalValue();
                
                // ...and to show the new temperature value
                calibTemperature[GuiVar_columnDHAutoCalibPointSelection] = GetColumnTemperaturePointFromGC(GuiVar_columnDHAutoCalibPointSelection + 1);
                SetEasyGUICalibrationPointTemperatureFromInternalValue(true);

#ifdef USE_ENABLE_SET_BUTTON_FLAG 
                enableSetButton = false;
#endif
                UpdateEasyGUIPage();
            }
            // 'else'- ignore
            break;

        default: // Not recognised - do nothing
            break;
    }
    
    return dealtWithTouch;
}


bool ColumnDHAutoCalibrationPageHandler::CalibratingNow(void)
{
    if((currentCalibrationState == INVALID) || (currentCalibrationState == IDLE)) {
        return false;
    }
    
    // 'else'
    return true;
}
    

/*
    Caller is telling us it is about to display the easyGUI 'ColumnDHAutoCalibrationPage',
    and that we should do whatever we have to do to get it ready,
    before the caller displays it.
    
    No arguments, no return code
*/
void ColumnDHAutoCalibrationPageHandler::DisplayingEasyGUIPage(void)
{
    strcpy(GuiVar_columnDHAutoCalibStabilisationTimeRemaining, notAvailable);

    SetEasyGUICalibrationPointTemperatureFromInternalValue(false);
    SetEasyGUIStabilisationTimeFromInternalValue();
    
    UpdateVolatileEasyGUIVariables();
}


/*
    (Re)display the easyGUI 'ColumnDHAutoCalibrationPage' -
    e.g. after the caller has updated one or more of the easyGUI variables
    included in the page, and wants to display the new value to the user.
    
    No arguments, no return code
*/
void ColumnDHAutoCalibrationPageHandler::UpdateEasyGUIPage(void)
{
    GetGCStatusLoop *getGCStatusLoop = GetGCStatusLoop::GetInstance();
    
    if(getGCStatusLoop != NULL) {
        // The Gas Calibration page has a status rectangle for the gas - 
        // GetGCStatusLoop can display this, we cannot
        getGCStatusLoop->ForceUpdateOfColumnDHAutoCalibrationPage();
    }
}

/*
    Update the easyGUI variable that displays the calibration temperature 
    to match the current value stored in our own internal variable.
    Note that there are two - one always displays the value stored in the GC,
    the other displays the value the user wants to set.
    
    Provided so that we do this consistently everywhere.
*/
void ColumnDHAutoCalibrationPageHandler::SetEasyGUICalibrationPointTemperatureFromInternalValue(bool updateBoth)
{
    // We display only the integer part of the temperature - pointless displaying the fractional part
    // if we only allow the user to change the value in increments of 1.0 (i.e. we give them
    // no way to change the fractional part)
    float temp = floor(calibTemperature[GuiVar_columnDHAutoCalibPointSelection] + 0.5f);
    sprintf(GuiVar_columnDHAutoCalibTemperatureToSet, "%1.0f", temp);
    
    if(updateBoth) {
        // But we do display the temperature obtained from the GC, to one decimal place
        // (since the GC returns it in that format)
        sprintf(GuiVar_columnDHAutoCalibCurrentTemp, "%1.1f", calibTemperature[GuiVar_columnDHAutoCalibPointSelection]);
    }
}


/*
    Update the easyGUI variable that displays the calibration point resistance
    to match the current value stored in our own internal variable. Also update
    the 'actual' resistance to show 'not available', since we have not calibrated yet.
    
    Intended to be called when the user selects a different calibration point.
    
    Provided so that we do this consistently everywhere.
*/
void ColumnDHAutoCalibrationPageHandler::SetEasyGUICalibrationPointResistanceFromInternalValue(void)
{
    if(calibResistance[GuiVar_columnDHAutoCalibPointSelection] >= 0.0f) {
        sprintf(GuiVar_columnDHAutoCalibCurrentRes, "%1.2f", calibResistance[GuiVar_columnDHAutoCalibPointSelection]);
    } else {
        strcpy(GuiVar_columnDHAutoCalibCurrentRes, notAvailable);
    }

    strcpy(GuiVar_columnDHAutoCalibResistance, notAvailable);
}


/*
    Update the easyGUI variable that displays the calibration stabilisation time
    to match the current value stored in our own internal variable.
    
    Provided so that we do this consistently everywhere
*/
void ColumnDHAutoCalibrationPageHandler::SetEasyGUIStabilisationTimeFromInternalValue(void)
{
    sprintf(GuiVar_columnDHAutoCalibStabilisationTimeToSet, "%1d", stabilisationTime);
}


/*
    Construct the GC command to start the calibration process, 
    using the current parameter values.
    
    Args: pointer to a buffer in which to store the command, as a null-terminated string
    
    No return code
*/
void ColumnDHAutoCalibrationPageHandler::ConstructStartCalibrationCommand(char* commandBuffer)
{
    // Command is "CCONnttt", where 'n' is the calibration point (1, 2 or 3),
    // and "ttt" is the temperature
    
    commandBuffer[0] = 'C';
    commandBuffer[1] = 'C';
    commandBuffer[2] = 'O';
    commandBuffer[3] = 'N';
    
    switch (GuiVar_columnDHAutoCalibPointSelection) {
        case 0:
            commandBuffer[4] = '1';
            break;
            
        case 1:
            commandBuffer[4] = '2';
            break;
        
        default: // Assume the index is 2
            commandBuffer[4] = '3';
            break;
    }
            
    sprintf(&commandBuffer[5], "%03.0f", calibTemperature[GuiVar_columnDHAutoCalibPointSelection]);
}


/*
    Construct the GC command to stop the calibration process.
    (Provided to match the 'ConstructStartCalibrationCommand' function.)
    
    Args: pointer to a buffer in which to store the command, as a null-terminated string
    
    No return code
*/
void ColumnDHAutoCalibrationPageHandler::ConstructStopCalibrationCommand(char* commandBuffer)
{
    // Command is "CCOF"
    
    commandBuffer[0] = 'C';
    commandBuffer[1] = 'C';
    commandBuffer[2] = 'O';
    commandBuffer[3] = 'F';
    commandBuffer[4] = '\0';
}


/*
    As the name implies, sends a command to the GC and returns the response.
    
    Args: pointer to a buffer containing the command, as a null-terminated string
          pointer to a buffer to contain the response, as a null-terminated string
          
    No return code (it is up to the caller to examine the response to see whether 
    the command succeeded or failed)
*/
void ColumnDHAutoCalibrationPageHandler::SendCommandToGCAndGetResponse(char* command, char* response)
{
#define USE_GC_UTILS // Testing new class
#ifdef USE_GC_UTILS
    USBHostGCUtilities::SendCommandToGCAndGetResponse(usbDevice, usbHostGC, command, response);
#else
    while(usbHostGC->ExecutingSetDeviceReport()) {}

    usbHostGC->SetDeviceReport(usbDevice, command, response);
//#define DEBUG_PRINT_HERE
#ifdef DEBUG_PRINT_HERE
    char dbg[100];
    sprintf(dbg, "SCTGC cmd \"%s\", response \"%s\"", command, response);
    SpecialDebugPrint(dbg, 10, 275);
#endif // DEBUG_PRINT_HERE
#endif // USE_GC_UTILS
}


/*
    Sends a command to the GC for which we expect a response of "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 responded with "DACK", false for anything else
*/
bool ColumnDHAutoCalibrationPageHandler::SendCommandToGCWithDACKResponse(char *cmd)
{
#define USE_GC_UTILS // Testing new class
#ifdef USE_GC_UTILS
    return USBHostGCUtilities::SendCommandToGCWithDACKResponse(usbDevice, usbHostGC, cmd);
#else
    char response[50];
    SendCommandToGCAndGetResponse(cmd, response);
    // We expect a response like this: "DACK" for success, "DNAK" for failure, "EPKT" for error
    
    return (response[1] == 'A');
#endif // USE_GC_UTILS 
}


/*
    Obtains the current calibration from the GC, and returns it as a value
    in the 'CalibrationState' enumeration.
*/
CalibrationState ColumnDHAutoCalibrationPageHandler::GetCalibrationState(void)
{
    char response[30];
    
    SendCommandToGCAndGetResponse("QDCS", response);
    
    // We expect a response of the form "DDCSnnnn", where "nnnn" is the calibration state:
    //   0000 = idle
    //   0001 = stage 1
    //   0002 = stage 1A
    //   0003 = stage 2
    //   0004 = stage 3

    if(response[0] != 'D') {
        return INVALID;
    }

    if(response[1] != 'D') {
        return INVALID;
    }

    if(response[2] != 'C') {
        return INVALID;
    }

    if(response[3] != 'S') {
        return INVALID;
    }

    if(response[4] != '0') {
        return INVALID;
    }

    if(response[5] != '0') {
        return INVALID;
    }

    if(response[6] != '0') {
        return INVALID;
    }
    
    switch (response[7]) {
        case '0':
            return IDLE;
            
        case '1':
            return STAGE_1;
        
        case '2':
            return STAGE_1A;
        
        case '3':
            return STAGE_2;
        
        case '4':
            return STAGE_3;
        
        default:
            return INVALID;
    }
}


/*
    Set the easyGUI variable that displays the calibration state,
    to correspond with the current mode.
    
    No arguments, no return value
*/
void ColumnDHAutoCalibrationPageHandler::SetEasyGUICalibrationState(void)
{
    switch (currentCalibrationState) {
        case IDLE:
            strcpy(GuiVar_columnDHAutoCalibCalibrationState, "Idle");
            break;

        case STAGE_1:
            strcpy(GuiVar_columnDHAutoCalibCalibrationState, "Heat to Calib + 10");
            break;

        case STAGE_1A:
            strcpy(GuiVar_columnDHAutoCalibCalibrationState, "Stabilising");
            break;

        case STAGE_2:
            strcpy(GuiVar_columnDHAutoCalibCalibrationState, "Cool to Calib");
            break;

        case STAGE_3:
            strcpy(GuiVar_columnDHAutoCalibCalibrationState, "Measuring");
            break;

        default:
            strcpy(GuiVar_columnDHAutoCalibCalibrationState, "Invalid");
            break;
    }
}

/*
    The Set Calibration button should be enabled during stage 3 and afterwards - but not before. 
    This function sets the colour of the "Set" text on the button appropriately.
    
    No arguments, no return code
*/
void ColumnDHAutoCalibrationPageHandler::ShowSetCalibrationButtonEnabledState(void)
{
#ifdef USE_ENABLE_SET_BUTTON_FLAG 
    if(enableSetButton) {
#else
    if((currentCalibrationState == STAGE_3) || (previousCalibrationState == STAGE_3)) {
#endif
        GuiVar_columnDHAutoCalibSetColour = 0; // Black - enabled
    } else {
        GuiVar_columnDHAutoCalibSetColour = SixteenBitColorValue(192, 192, 192); // Light grey - disabled
    }
}


/*
    Set the easyGUI variable that displays the heat state (i.e. on/off),
    to correspond with the current state.
    
    No arguments, no return value
*/
void ColumnDHAutoCalibrationPageHandler::SetEasyGUIHeatState(void)
{
    char response[30];
    
    SendCommandToGCAndGetResponse("QHTS", response);
    
    bool heatIsOn = false;
    
    // We expect a response like "DHTS000n", where 'n' is '1' for heat on,
    // '0' for heat off. Guard against "EPKT" as well
    if((response[0] == 'D') && (strlen(response) >= 8) && (response[7] == '1')) {
        heatIsOn = true;
    }
    
    strcpy(GuiVar_columnDHAutoCalibHeatState, heatIsOn ? "On" : "Off");
}


/*
    Set the easyGUI variable that displays the oven temperature
    to correspond with the current oven temperature 
    (i.e. not a calibration point value, but the actual temperature).
    
    No arguments, no return value
*/
void ColumnDHAutoCalibrationPageHandler::SetEasyGUIOvenTemperature(void)
{
    char response[30];
    
    SendCommandToGCAndGetResponse("QCOL", response);
    
    // We expect a response like "DCOLnnnn", where "nnnn" 
    // is the current oven temperaure in units of 0.1 deg C
    // Guard against "EPKT" as well
    
    strcpy(GuiVar_columnDHAutoCalibActualOvenTemp, notAvailable); // default
    
    if(response[0] != 'D') {
        return;
    }
    if(response[1] != 'C') {
        return;
    }
    if(response[2] != 'O') {
        return;
    }
    if(response[3] != 'L') {
        return;
    }
    
    GuiVar_columnDHAutoCalibActualOvenTemp[0] = response[4];
    GuiVar_columnDHAutoCalibActualOvenTemp[1] = response[5];
    GuiVar_columnDHAutoCalibActualOvenTemp[2] = response[6];
    GuiVar_columnDHAutoCalibActualOvenTemp[3] = '.';
    GuiVar_columnDHAutoCalibActualOvenTemp[4] = response[7];
    GuiVar_columnDHAutoCalibActualOvenTemp[5] = '\0';
}


/*
    Gets the current oven temperature, returns it as a floating point value
    
    No arguments
    
    Return value: the current oven temperature, as a value of type float
*/
float ColumnDHAutoCalibrationPageHandler::GetCurrentOvenTemperature(void)
{
    char response[30];
    
    SendCommandToGCAndGetResponse("QCOL", response);
    
    // We expect a response like "DCOLnnnn", where "nnnn" 
    // is the current oven temperaure in units of 0.1 deg C
    // Guard against "EPKT" as well
    
    if(response[0] != 'D') {
        return -1.0f;
    }
    if(response[1] != 'C') {
        return -1.0f;
    }
    if(response[2] != 'O') {
        return -1.0f;
    }
    if(response[3] != 'L') {
        return -1.0f;
    }
    
    float temp;
    sscanf(&response[4], "%f", &temp);

    return (temp / 10.0f);
}


/*
    Set the easyGUI variable that displays the calibration time remaining
    to the correct value - according to whether we are actually calibrating now
    or not.
    
    No arguments, no return code.
*/
void ColumnDHAutoCalibrationPageHandler::SetEasyGUICalibrationTimeRemaining(void)
{
    if(CalibratingNow()) {
        // "Time remaining" is stabilisation time remaining - applies only in calibration stage 1A
        if(currentCalibrationState == STAGE_1A) {
            if(previousCalibrationState == STAGE_1) {
#ifdef USE_STABILISATION_TIMER
                stabilisationTimer.reset();
                stabilisationTimer.start();
#else
                gcTimeStabilisationStarted = GetTimeFromGC();
#endif
            }
#ifdef USE_STABILISATION_TIMER
            double timeTakenSoFarInSeconds = ((double)stabilisationTimer.read_ms()) / 1000.0; // Note that we zeroed the timer when we entered stage 1A
            int timeTakenSoFarInMinutes = (int) floor((timeTakenSoFarInSeconds / 60.0) + 0.5);
#else
            float gcTimeNow = GetTimeFromGC();
            float timeTakenSoFarInMinutes = gcTimeNow - gcTimeStabilisationStarted;
#endif
            sprintf(GuiVar_columnDHAutoCalibStabilisationTimeRemaining, "%1.1f", ((float)stabilisationTime - timeTakenSoFarInMinutes));
        } else {
            if (previousCalibrationState == STAGE_1A) {
#ifdef USE_STABILISATION_TIMER
                stabilisationTimer.stop();
#endif
                strcpy(GuiVar_columnDHAutoCalibStabilisationTimeRemaining, notAvailable);
            }
        }  
    } else {
        strcpy(GuiVar_columnDHAutoCalibStabilisationTimeRemaining, notAvailable);
    }
}


/*
    This updates all the easyGUI variables that display something that may change
    while this page is displayed, without this page's knowledge. It is intended 
    that will (e.g.) be called as part of GetGCStatusLoop's regular polling of the GC's state.
    
    An important consideration, which affects the way we treat each of the easyGUI variables,
    is the current calibration state of the GC.
    
    No arguments, no return code
*/
void ColumnDHAutoCalibrationPageHandler::UpdateVolatileEasyGUIVariables(void)
{
    previousCalibrationState = currentCalibrationState;

    currentCalibrationState = GetCalibrationState();
        
    SetEasyGUICalibrationState();
    ShowSetCalibrationButtonEnabledState();
    SetEasyGUIHeatState();
    SetEasyGUIOvenTemperature();
    
    SetEasyGUICalibrationTimeRemaining();

    if(CalibratingNow()) {
        strcpy(GuiVar_columnDHAutoCalibCalibrateButtonText, "Stop");
        
        CanUnlockDoorActuators(false); // Not while we are calibrating
    } else {
        strcpy(GuiVar_columnDHAutoCalibCalibrateButtonText, "Calibrate");
        
        CanUnlockDoorActuators(true); // We are not calibrating
    }
    
//    if((currentCalibrationState == STAGE_3) || (previousCalibrationState == STAGE_3)) {
//  No - only *in* stage 3, not after - buffer length (for resistance measurement) in embedded code may have changed
    if(currentCalibrationState == STAGE_3) {
        // Can now get directly heated column resistance
        
        if(needToGetColumnResistance) { // But do not get it twice
// Do we need to get the column resistance ourselves, and give the user the option to update the current calibration point afterwards, 
// or does the embedded code measure the resistance, then update the current point, automatically by itself in stage 3?
// If we don't do "CCSM", does it ever come out of calibration stage 3?
//#define GET_COLUMN_RESISTANCE_OURSELVES
#ifdef GET_COLUMN_RESISTANCE_OURSELVES
            DisplayGettingColumnResistancePage(); // Tell user what we are doing
            // Send CCSM command
            TellGCToMeasureColumnResistance();
            // Wait 2 seconds + a margin
            Thread::wait(2100);
            actualColumnResistance = GetActualColumnResistanceFromGC();
            sprintf(GuiVar_columnDHAutoCalibResistance, "%.2f", actualColumnResistance);
#ifdef USE_ENABLE_SET_BUTTON_FLAG 
            enableSetButton = true;
#endif
            // TODO: Update 'New' temperature and resistance - user can then decide 
            // whether or not to press "Set" to copy those values to the current point
            // GuiVar_columnDHAutoCalibTemperatureToSet for temperature
            // GuiVar_columnDHAutoCalibResistance for resistance
            sprintf(GuiVar_columnDHAutoCalibTemperatureToSet, "%.0f", GetCurrentOvenTemperature());
            sprintf(GuiVar_columnDHAutoCalibResistance, "%.2f", actualColumnResistance);
        
            RedisplayColumnCalibrationPage(); // Normal service is resumed
#endif // GET_COLUMN_RESISTANCE_OURSELVES
            needToGetColumnResistance = false;
        }
    } 
    
#ifndef GET_COLUMN_RESISTANCE_OURSELVES
    if(previousCalibrationState == STAGE_3){
        // Update the displayed temperature and resistance for the currently selected point
        calibTemperature[GuiVar_columnDHAutoCalibPointSelection] = GetColumnTemperaturePointFromGC(GuiVar_columnDHAutoCalibPointSelection + 1);
        SetEasyGUICalibrationPointTemperatureFromInternalValue(true);
        
        calibResistance[GuiVar_columnDHAutoCalibPointSelection] = GetColumnResistancePointFromGC(GuiVar_columnDHAutoCalibPointSelection + 1);
        SetEasyGUICalibrationPointResistanceFromInternalValue();
    }
#endif

    // Bodge to fix problem where the calibrated resistance is initially displayed as "99.99" - 
    // this is the initial value that easyGUI assigns to that variable
    if(strcmp(GuiVar_columnDHAutoCalibCurrentRes, "99.99") == 0) {
        SetEasyGUICalibrationPointResistanceFromInternalValue();
    }
}


/*
    Gets the current stabilisation time from the GC, returns it as an integer
    
    No arguments
    
    Return value: the stabilisation time obtained from the GC, or -1 if this failed
*/
int ColumnDHAutoCalibrationPageHandler::GetStabilisationTimeFromGC(void)
{
    char response[30];
    
    SendCommandToGCAndGetResponse("GCST", response);
    
    // We expect a response like "DCST00nn", where "nn" is the stabilisation time
    // in minutes, 0 to 99
    if(response[0] != 'D') {
        return -1;
    }
        
    if(response[1] != 'C') {
        return -1;
    }
        
    if(response[2] != 'S') {
        return -1;
    }
        
    if(response[3] != 'T') {
        return -1;
    }
        
    if(response[4] != '0') {
        return -1;
    }
        
    if(response[5] != '0') {
        return -1;
    }
    
    int retVal;
    sscanf(&response[6], "%d", &retVal);
    return retVal;    
}


/*
    Sets the current stabilisation time in the GC from the integer value passed to it
    
    Argument: the new value for the stabilisation time, in (whole) minutes
    
    Returns true if the GC responded with "DACK", false if anything else
*/
bool ColumnDHAutoCalibrationPageHandler::SetStabilisationTimeInGC(int value)
{
    char commandBuffer[30];
    
    sprintf(commandBuffer, "SCST%04d", value); 
    
    return SendCommandToGCWithDACKResponse(commandBuffer);
}


/*
    Gets and returns the temperature value for the specified calibration point.
    
    Argument: the index of the required calibration point (1, 2 or 3)
    
    Return code: the corresponding temperature value, as a floating point value 
                 (the temperature is returned in units of 1/10 ohm)
*/
float ColumnDHAutoCalibrationPageHandler::GetColumnTemperaturePointFromGC(int pointIndex)
{
    char commandBuffer[30];

    commandBuffer[0] = 'G';
    commandBuffer[1] = 'C';
    commandBuffer[2] = 'T';

    switch(pointIndex) {
        case 2:
            commandBuffer[3] = '2';
            break;
        case 3:
            commandBuffer[3] = '3';
            break;
        default: // Assume 1
            commandBuffer[3] = '1';
            break;
            
    }
    commandBuffer[4] = '\0';

    char response[30];
    SendCommandToGCAndGetResponse(commandBuffer, response);
    
    // We expect a response like "DCRnrrrr", where 'n' is the point index 
    // and "rrrr" is the resistance in ohms multiplied by 100
    if(response[0] != 'D') {
        return -1.0f;
    }
        
    if(response[1] != 'C') {
        return -1.0f;
    }
        
    if(response[2] != 'T') {
        return -1.0f;
    }
        
    if(response[3] != commandBuffer[3]) {
        return -1.0f;
    }

    int temp;
    sscanf(&response[4], "%d", &temp);
    
    float retVal = (float) temp;
    retVal /= 10.0f;
    return retVal;        
}

/*
    Sets the temperature value for the specified calibration point.
    
    Arguments: the index of the required calibration point (1, 2 or 3)
               the temperature value to set (deg C)
    
    No return code
*/
void ColumnDHAutoCalibrationPageHandler::SetColumnTemperaturePointInGC(int pointIndex, float temperatureToSet)
{
    char commandBuffer[30];

    commandBuffer[0] = 'S';
    commandBuffer[1] = 'C';
    commandBuffer[2] = 'T';

    switch(pointIndex) {
        case 2:
            commandBuffer[3] = '2';
            break;
        case 3:
            commandBuffer[3] = '3';
            break;
        default: // Assume 1
            commandBuffer[3] = '1';
            break;
            
    }
    
    // Value is sent in units of 1/10 deg C
    double dTemp = (double) temperatureToSet * 10.0;
    int valueToSend = (int) floor(dTemp + 0.5);
    sprintf(&commandBuffer[4], "%04d", valueToSend);
    
    commandBuffer[8] = '\0';

    char response[30];
    SendCommandToGCAndGetResponse(commandBuffer, response);
}


/*
    Gets and returns the resistance value for the specified calibration point.
    
    Argument: the index of the required calibration point (1, 2 or 3)
    
    Return code: the corresponding resistance value, as a floating point value 
                 (the resistance is returned in units of 1/100 ohm)
*/
float ColumnDHAutoCalibrationPageHandler::GetColumnResistancePointFromGC(int pointIndex)
{
    char commandBuffer[30];

    commandBuffer[0] = 'G';
    commandBuffer[1] = 'C';
    commandBuffer[2] = 'R';

    switch(pointIndex) {
        case 2:
            commandBuffer[3] = '2';
            break;
        case 3:
            commandBuffer[3] = '3';
            break;
        default: // Assume 1
            commandBuffer[3] = '1';
            break;
            
    }
    commandBuffer[4] = '\0';

    char response[30];
    SendCommandToGCAndGetResponse(commandBuffer, response);
    
    // We expect a response like "DCRnrrrr", where 'n' is the point index 
    // and "rrrr" is the resistance in ohms multiplied by 100
    if(response[0] != 'D') {
        return -1.0f;
    }
        
    if(response[1] != 'C') {
        return -1.0f;
    }
        
    if(response[2] != 'R') {
        return -1.0f;
    }
        
    if(response[3] != commandBuffer[3]) {
        return -1.0f;
    }

    int temp;
    sscanf(&response[4], "%d", &temp);
    
    float retVal = (float) temp;
    retVal /= 100.0f;
    return retVal;        
}

/*
    Sets the resistance value for the specified calibration point.
    
    Arguments: the index of the required calibration point (1, 2 or 3)
               the corresponding resistance value (in ohms)
    
    No return code
*/
void ColumnDHAutoCalibrationPageHandler::SetColumnResistancePointInGC(int pointIndex, float resistanceToSet)
{
    char commandBuffer[30];

    commandBuffer[0] = 'S';
    commandBuffer[1] = 'C';
    commandBuffer[2] = 'R';

    switch(pointIndex) {
        case 2:
            commandBuffer[3] = '2';
            break;
        case 3:
            commandBuffer[3] = '3';
            break;
        default: // Assume 1
            commandBuffer[3] = '1';
            break;
            
    }
    
    // Value is sent in units of 1/100 deg C
    double dTemp = (double) resistanceToSet * 100.0;
    int valueToSend = (int) floor(dTemp + 0.5);
    sprintf(&commandBuffer[4], "%04d", valueToSend);
    
    commandBuffer[8] = '\0';


    char response[30];
    SendCommandToGCAndGetResponse(commandBuffer, response);
}


/*
    Gets and returns the actual resistance of the directly heated column.
    Note that this will only be valid during and after calibration stage 3. 
    
    No arguments
    
    Return code: the column resistance, as a floating point value 
                 (the resistance is returned in units of 1/100 ohm)
*/
float ColumnDHAutoCalibrationPageHandler::GetActualColumnResistanceFromGC(void)
{
    char commandBuffer[30];

    commandBuffer[0] = 'Q';
    commandBuffer[1] = 'D';
    commandBuffer[2] = 'C';
    commandBuffer[3] = 'R';

    commandBuffer[4] = '\0';


    char response[30];
    SendCommandToGCAndGetResponse(commandBuffer, response);
    
    // We expect a response like "DDCrrrrr", where "rrrrr" (note 5 digits, not 4)
    // is the resistance in ohms multiplied by 100
    if(response[0] != 'D') {
        return -1.0f;
    }
        
    if(response[1] != 'D') {
        return -1.0f;
    }
        
    if(response[2] != 'C') {
        return -1.0f;
    }
        
    int temp;
    sscanf(&response[3], "%d", &temp);
    
    float retVal = (float) temp;
    retVal /= 100.0f;
    return retVal;        
}


/*
    Sends a "CCSM" command to the GC, to tell it to start the process of measuring the column resistance.
    *** We cannot request the resistance value until at least two seconds after this. ***
    
    No arguments.
    
    Returns true if the command succeeded ("DACK" response), false if not.
*/
bool ColumnDHAutoCalibrationPageHandler::TellGCToMeasureColumnResistance(void)
{
    char commandBuffer[20];
    
    commandBuffer[0] = 'C';
    commandBuffer[1] = 'C';
    commandBuffer[2] = 'S';
    commandBuffer[3] = 'M';
    commandBuffer[4] = '\0';
    
    return SendCommandToGCWithDACKResponse(commandBuffer);
}


#ifndef USE_STABILISATION_TIMER
/*
    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.

    Returns the time obtained from the GC, or -1 if this failed
*/
float ColumnDHAutoCalibrationPageHandler::GetTimeFromGC(void)
{
    char response[50];
    SendCommandToGCAndGetResponse("QTIM", response);

    if(response[0] == 'E') {
        // Got "EPKT" response
        return -1.0f;
    }
    
    // Assume we 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[10];
    buff[0] = response[4];
    buff[1] = response[5];
    buff[2] = response[6];
    buff[3] = '.';
    buff[4] = response[7];
    buff[5] = '\0';
    
    float gcTime;
    sscanf(buff, "%f", &gcTime);
    
    return gcTime;
}
#endif


/*
    Displays the 'getting column resistance' page. 
    
    At this point, we have issued the "CCSM" command to the GC, to start the process
    of getting the column resistance. We have to wait two seconds before we can get
    the resistance value. We display this page during those two seconds, to make it clear
    to the user that we cannot do anything else during that time.
    
    After getting the resistance, call 'RedisplayColumnCalibrationPage'.
    
    No args, no return code.
*/
void ColumnDHAutoCalibrationPageHandler::DisplayGettingColumnResistancePage(void)
{
#ifdef USING_BACKGROUND_BITMAP
    DrawBackgroundBitmap(); 
#else
    GuiLib_Clear();
#endif

    GuiLib_ShowScreen(GuiStruct_GettingColumnResistancePage_Def, GuiLib_NO_CURSOR, GuiLib_RESET_AUTO_REDRAW);

    GuiLib_Refresh();   
}


/*
    After issuing the "CCSM" command, we display the 'getting column resistance' page, 
    wait two seconds, then get the column resistance. During this period, we display the 
    "Getting Column Resistance" page. After this, we redisplay our normal page,
    by calling this function.
    
    This function currently contains only a single function call. It is implemented 
    for clarity in the code - and in case we decide something more complicated 
    is required in future.
    
    No args, no return code.
*/
void ColumnDHAutoCalibrationPageHandler::RedisplayColumnCalibrationPage(void)
{
    UpdateVolatileEasyGUIVariables();
    
    UpdateEasyGUIPage();
}