Repository for import to local machine
Dependencies: DMBasicGUI DMSupport
GetGCStatusLoop.cpp
- Committer:
- jmitc91516
- Date:
- 2017-07-20
- Revision:
- 2:6e94a7fd1e37
- Parent:
- 1:a5258871b33d
- Child:
- 3:010aeeacd7d7
File content as of revision 2:6e94a7fd1e37:
#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"; //#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 // .... /* 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; } // 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 columnDHTempProfilePageGraph; 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; #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); columnDHTempProfilePageGraph = new GuiLibGraph(5); 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(¤tColumnMethodRunTime); 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(¤tGasMethodRunTime); 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(¤tInjectorMethodRunTime); 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_ColumnTempProfilePage_60) && (currentPage != GuiStruct_ColumnDHPage1_40) && (currentPage != GuiStruct_ColumnDHPage2_50) && (currentPage != GuiStruct_ColumnDHTempProfilePage_61) && (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) { temp[index++] = ' '; temp[index++] = 'd'; temp[index++] = 'e'; temp[index++] = 'g'; temp[index++] = ' '; temp[index++] = 'C'; } } 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, 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, 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, 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) { maxTemp[index++] = ' '; maxTemp[index++] = 'd'; maxTemp[index++] = 'e'; maxTemp[index++] = 'g'; maxTemp[index++] = ' '; maxTemp[index++] = 'C'; } } 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]; if(DoorActuatorButtonsHaveChanged(usbDevice, usbHostGC)) { mustUpdateDisplay = true; } SetupDoorActuatorCommandUserInterface(usbDevice, usbHostGC, true); if(columnType == DIRECTLY_HEATED_COLUMN) { GetDirectlyHeatedColumnTemperature(buff, false); if(strcmp(buff, GuiVar_columnDHTemperature) != 0) { strcpy(GuiVar_columnDHTemperature, buff); //mustUpdateDisplay = true; // No - just do this (don't force entire rectangle to redisplay) // Hard coded values taken from easyGUI if(pageNumber == GuiStruct_ColumnDHPage1_40) { RedrawSingleEasyGUIVariableOnComponentPage(450, 110, &GuiVar_columnDHTemperature, GuiLib_ALIGN_LEFT, COLUMN); } // else must be GuiStruct_ColumnPage1_2 - should not display this variable on this page } } 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 if(pageNumber == GuiStruct_ColumnDHPage1_40) { RedrawSingleEasyGUIVariableOnComponentPage(450, 160, &GuiVar_columnTemperature, GuiLib_ALIGN_LEFT, COLUMN); } else { // Assume GuiStruct_ColumnPage1_2 RedrawSingleEasyGUIVariableOnComponentPage(400, 110, &GuiVar_columnTemperature, GuiLib_ALIGN_LEFT, COLUMN); } } GetColumnTargetTemperature(buff, "%s deg C"); 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 if(pageNumber == GuiStruct_ColumnDHPage1_40) { RedrawSingleEasyGUIVariableOnComponentPage(450, 210, &GuiVar_columnTargetTemperature2, GuiLib_ALIGN_LEFT, COLUMN); } else { // Assume GuiStruct_ColumnPage1_2 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 if(pageNumber == GuiStruct_ColumnDHPage1_40) { RedrawSingleEasyGUIVariableOnComponentPage(450, 280, &GuiVar_columnMaxTemp2, GuiLib_ALIGN_LEFT, COLUMN); } else { // Assume GuiStruct_ColumnPage1_2 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 if(pageNumber == GuiStruct_ColumnDHPage1_40) { RedrawSingleEasyGUIVariableOnComponentPage(450, 340, &GuiVar_columnStatus, GuiLib_ALIGN_LEFT, COLUMN); } else { // Assume GuiStruct_ColumnPage1_2 RedrawSingleEasyGUIVariableOnComponentPage(400, 290, &GuiVar_columnStatus, GuiLib_ALIGN_LEFT, COLUMN); } } if(SinglePageGCComponentStatusHasChanged(COLUMN, lastColumnStatusDisplayedOnColumnPage)) { mustUpdateDisplay = true; } if(mustUpdateDisplay) { // Reduce display flickering - get the component status from the GC // *before* we call GuiLib_Clear() if(singleGCComponentPageStatusColorAreas != NULL) { UpdateSingleGCComponentPageStatusColorArea(COLUMN); } //#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 column 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 if(singleGCComponentPageStatusColorAreas != NULL) { singleGCComponentPageStatusColorAreas->DisplayGCComponentStatus(COLUMN); lastColumnStatusDisplayedOnColumnPage = singleGCComponentPageStatusColorAreas->GetGCComponentStatus(COLUMN); } // 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); SetupDoorActuatorCommandUserInterface(usbDevice, usbHostGC, false); 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) 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]; if(DoorActuatorButtonsHaveChanged(usbDevice, usbHostGC)) { mustUpdateDisplay = true; } SetupDoorActuatorCommandUserInterface(usbDevice, usbHostGC, true); GetColumnType(buff); if(strcmp(buff, GuiVar_columnType) != 0) { mustUpdateDisplay = true; strcpy(GuiVar_columnType, buff); } 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); } GetSerialNumber(buff); if(strcmp(buff, GuiVar_columnSerialNumber) != 0) { mustUpdateDisplay = true; strcpy(GuiVar_columnSerialNumber, buff); } GetInjectionCountSinceColumnInstalled(buff); if(strcmp(buff, GuiVar_columnInjectionsSinceInstallation) != 0) { mustUpdateDisplay = true; strcpy(GuiVar_columnInjectionsSinceInstallation, buff); } 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); } //#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 column 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 if(singleGCComponentPageStatusColorAreas != NULL) { singleGCComponentPageStatusColorAreas->DisplayGCComponentStatus(COLUMN); } // 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); SetupDoorActuatorCommandUserInterface(usbDevice, usbHostGC, false); 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 } } /* 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)"); } } /* We have two column temperature profile graphs in the easyGUI project - one for the conventional column (index 4), and one for the directly heated column (index 5). This function returns a pointer to the appropriate one. Note that it returns NULL if there is no column - caller must check for this. ************************** Args: the required column type (the ColumnType enumeration - see GCComponentTypeEnums.h) Returns a pointer to the appropriate GuiLibGraph object, or NULL (see above) */ GuiLibGraph* GetGCStatusLoop::GetColumnTempGraphForColumnType(ColumnType columnType) { switch (columnType) { case DIRECTLY_HEATED_COLUMN: return columnDHTempProfilePageGraph; case CONVENTIONAL_COLUMN: return columnTempProfilePageGraph; default: return NULL; } } /* 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 = GetColumnTempGraphForColumnType(columnType); 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 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); 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 if(singleGCComponentPageStatusColorAreas != NULL) { singleGCComponentPageStatusColorAreas->DisplayGCComponentStatus(COLUMN); } // And (currently) we draw the component bitmap on top of the rectangle if(qspiBitmaps != NULL) { qspiBitmaps->DisplayColumnComponentBitmap(); } GuiLibGraph* graphToUse = GetColumnTempGraphForColumnType(columnType); 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); // 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 // 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(); } SetupDoorActuatorCommandUserInterface(usbDevice, usbHostGC, false); 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 if(currentPage == GuiStruct_ColumnDHPage1_40) { DisplayColumnPageData(true, DIRECTLY_HEATED_COLUMN, GuiStruct_ColumnDHPage1_40); } // 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, "%s deg C"); 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) { // 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 //#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 if(singleGCComponentPageStatusColorAreas != NULL) { singleGCComponentPageStatusColorAreas->DisplayGCComponentStatus(INJECTOR); lastInjectorStatusDisplayedOnInjectorPage = singleGCComponentPageStatusColorAreas->GetGCComponentStatus(INJECTOR); } // 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 } } /* 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(); } /* 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) { // 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 //#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 if(singleGCComponentPageStatusColorAreas != NULL) { singleGCComponentPageStatusColorAreas->DisplayGCComponentStatus(INJECTOR); } // And (currently) we draw the component bitmap on top of the rectangle if(qspiBitmaps != NULL) { qspiBitmaps->DisplayInjectorComponentBitmap(); } injectorTempProfilePageGraph->Redraw(); 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); } /* Displays the data on the injector gas status 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::DisplayInjectorGasStatusPageData(bool mustUpdateDisplay) { char buff[40]; GetInjectorSplitTime(buff); if(strcmp(buff, GuiVar_injectorSplitTime) != 0) { mustUpdateDisplay = true; strcpy(GuiVar_injectorSplitTime, buff); } GetSplitFlow(buff); if(strcmp(buff, GuiVar_injectorSplitFlowRate) != 0) { mustUpdateDisplay = true; strcpy(GuiVar_injectorSplitFlowRate, buff); } GetSplitRatio(buff); if(strcmp(buff, GuiVar_injectorSplitRatio) != 0) { mustUpdateDisplay = true; strcpy(GuiVar_injectorSplitRatio, buff); } if(SinglePageGCComponentStatusHasChanged(INJECTOR)) { mustUpdateDisplay = true; } 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 //#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 if(singleGCComponentPageStatusColorAreas != NULL) { singleGCComponentPageStatusColorAreas->DisplayGCComponentStatus(INJECTOR); } // And (currently) we draw the component bitmap on top of the rectangle if(qspiBitmaps != NULL) { qspiBitmaps->DisplayInjectorComponentBitmap(); } GuiLib_ShowScreen(GuiStruct_InjectorGasStatusPage_30, 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 } } /* 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)); } /* Displays the data on the injector consumables 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::DisplayInjectorConsumablesPageData(bool mustUpdateDisplay) { // EasyGUIDebugPrint("Injector Page", 100, 100); // Injector temperature and mode char buff[40]; GetInjectionCountSinceLinerChanged(buff); if(strcmp(buff, GuiVar_injectionCountSinceLinerChanged) != 0) { mustUpdateDisplay = true; strcpy(GuiVar_injectionCountSinceLinerChanged, buff); } GetInjectionCountSinceSeptaChanged(buff); if(strcmp(buff, GuiVar_injectionCountSinceSeptaChanged) != 0) { mustUpdateDisplay = true; strcpy(GuiVar_injectionCountSinceSeptaChanged, buff); } GetInjectionCountSinceOringChanged(buff); if(strcmp(buff, GuiVar_injectionCountSinceOringChanged) != 0) { mustUpdateDisplay = true; strcpy(GuiVar_injectionCountSinceOringChanged, buff); } GetComponentStatusString(INJECTOR, buff); if(strcmp(buff, GuiVar_injectorStatus) != 0) { mustUpdateDisplay = true; strcpy(GuiVar_injectorStatus, buff); } if(SinglePageGCComponentStatusHasChanged(INJECTOR)) { mustUpdateDisplay = true; } 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 //#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 if(singleGCComponentPageStatusColorAreas != NULL) { singleGCComponentPageStatusColorAreas->DisplayGCComponentStatus(INJECTOR); } // And (currently) we draw the component bitmap on top of the rectangle if(qspiBitmaps != NULL) { qspiBitmaps->DisplayInjectorComponentBitmap(); } GuiLib_ShowScreen(GuiStruct_InjectorConsumablesPage_20, 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 } } /* 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, "%s deg C"); 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) { // 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 //#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 detector 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 if(singleGCComponentPageStatusColorAreas != NULL) { singleGCComponentPageStatusColorAreas->DisplayGCComponentStatus(DETECTOR); lastDetectorStatusDisplayedOnDetectorPage = singleGCComponentPageStatusColorAreas->GetGCComponentStatus(DETECTOR); } // 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) { // 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 if(singleGCComponentPageStatusColorAreas != NULL) { singleGCComponentPageStatusColorAreas->DisplayGCComponentStatus(GAS); lastGasStatusDisplayedOnGasInformationPage = singleGCComponentPageStatusColorAreas->GetGCComponentStatus(GAS); } // 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 } } /* 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(); } 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) { // 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 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 if(singleGCComponentPageStatusColorAreas != NULL) { singleGCComponentPageStatusColorAreas->DisplayGCComponentStatus(GAS); } // And (currently) we draw the component bitmap on top of the rectangle if(qspiBitmaps != NULL) { qspiBitmaps->DisplayGasComponentBitmap(); } gasFlowProfilePageGraph->Redraw(); 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(¤tColumnMethodRunTime); // 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(¤tColumnMethodRunTime); // 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(¤tColumnMethodRunTime); //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, "%s deg C"); 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 // 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_ColumnTempProfilePage_60: DisplayColumnTempProfilePageData(mustUpdateDisplay, CONVENTIONAL_COLUMN, GuiStruct_ColumnTempProfilePage_60); break; case GuiStruct_ColumnDHPage1_40: DisplayColumnPageData(mustUpdateDisplay, DIRECTLY_HEATED_COLUMN, GuiStruct_ColumnDHPage1_40); break; case GuiStruct_ColumnDHPage2_50: DisplayColumnInformationPageData(mustUpdateDisplay, DIRECTLY_HEATED_COLUMN, GuiStruct_ColumnDHPage2_50); break; case GuiStruct_ColumnDHTempProfilePage_61: DisplayColumnTempProfilePageData(mustUpdateDisplay, DIRECTLY_HEATED_COLUMN, GuiStruct_ColumnDHTempProfilePage_61); 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_InjectorTempProfilePage_25: DisplayInjectorTempProfilePageData(mustUpdateDisplay); break; case GuiStruct_InjectorGasStatusPage_30: DisplayInjectorGasStatusPageData(mustUpdateDisplay); break; case GuiStruct_InjectorConsumablesPage_20: DisplayInjectorConsumablesPageData(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_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, "%s deg C"); GetDirectlyHeatedColumnTemperature(GuiVar_columnDHTemperature, false); 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); GetInjectionCountSinceColumnInstalled(GuiVar_columnInjectionsSinceInstallation); // Injector page GetInjectorTemperature(GuiVar_injectorTemperature, false); GetInjectorTargetTemperature(GuiVar_injectorTargetTemperature2, "%s deg C"); GetInjectionMode(GuiVar_injectionMode2); GetInjectorType(GuiVar_injectorType, false); GetComponentStatusString(INJECTOR, GuiVar_injectorStatus); // Injector Gas Status page GetInjectorSplitTime(GuiVar_injectorSplitTime); GetSplitFlow(GuiVar_injectorSplitFlowRate); GetSplitRatio(GuiVar_injectorSplitRatio); // Injector Consumables page GetInjectionCountSinceLinerChanged(GuiVar_injectionCountSinceLinerChanged); GetInjectionCountSinceSeptaChanged(GuiVar_injectionCountSinceSeptaChanged); GetInjectionCountSinceOringChanged(GuiVar_injectionCountSinceOringChanged); // Detector page(s) GetDetectorType(GuiVar_detectorType2, false, false); GetDetectorRange(GuiVar_detectorRange); GetFuelFlowRate(GuiVar_detectorFuelFlowRate); GetAirFlowRate(GuiVar_detectorAirFlowRate); GetDetectorIgnitionState(GuiVar_detectorStatus); GetDetectorTemperature(GuiVar_detectorTemperature); 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); // 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; } /* 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(); } 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(); 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) } 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(); //#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(ð); } // 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 }