def ProcessExitKey(zone):

    #  global GD.exitPending

    # Keep the current mode so we can check if it changes and we need to re-display the keyboard.
    lastCurrentMode = GD.currentMode

    # If the data has not changed or the user has pressed EXIT at the 'Save or Exit?' prompt we will exit back to a select mode.
    if zones.zoneTimes.CheckIfDataChanged() == False or GD.exitPending == True:

        # Clear info messages.
        display.DisplayBottomRightInfoPrompt()
        display.DisplayBottomLeftInfoPrompt(GD.BLANK_PROMPT)
        display.DisplayZoneMode()

        # No exit pending now.
        GD.exitPending = False

        # Move back to a select mode. Use correct select for rad, ufh or sys.
        if zone < 14:
            ProcessRadKey(zone)
        elif zone < 30:
            ProcessUfhKey(zone)
        else:
            keysSystem.ProcessImmersionTimesKey(zone)

    # We have changed the data so we will set exit pending flag and prompt for save or exit.
    else:
        GD.exitPending = True
        display.DisplayBottomLeftInfoPrompt()

    return 1
def ProcessDigitKey(key):

    # Only process key if we are in a time on or off edit mode.
    if GD.currentMode in (GD.MODE_PROG_ON_AT, GD.MODE_PROG_OFF_AT):

        # Convert keycode to character '0' to '9'
        key = chr(key - 2)

        if GD.currentMode == GD.MODE_PROG_ON_AT:
            # Update and display the on time. Call to modify will return -ve when all digits done and +ve if OK.
            status = zones.zoneTimes.ModifyTime(GD.ON_TIME_INDEX, key)
            display.DisplayProgOnTime()

        else:
            # Update and display the off time. Call to modify will return -ve when all digits done and +ve if OK.
            status = zones.zoneTimes.ModifyTime(GD.OFF_TIME_INDEX, key)
            display.DisplayProgOffTimeAndDays()

        # Update save and valid prompts now we have changed the data
        display.DisplayBottomLeftInfoPrompt()
        display.DisplayBottomRightInfoPrompt()

        # Check to see if we have edited all the digits.
        if status < 0:
            # Return to time programming mode.
            GD.currentMode = GD.MODE_PROG_TIME
            display.DisplayKeyboardImage(useMode=GD.currentMode)

    return 1
def SwitchToRunMode():

    # Clear all the prompts.
    display.DisplayZoneMode()
    display.TopRightInfoPrompt(GD.BLANK_PROMPT)
    display.DisplayProgOnTime(GD.BLANK_PROMPT)
    display.DisplayProgOffTimeAndDays(GD.BLANK_PROMPT)
    display.DisplayEntries(GD.BLANK_PROMPT)
    display.DisplayBottomLeftInfoPrompt(GD.BLANK_PROMPT)
    display.DisplayBottomRightInfoPrompt(GD.BLANK_PROMPT)

    # Set system bits as required.
    system.SetModeOutputControlBitsFromConfigBits()

    # Switch to run mode.
    GD.currentMode = GD.MODE_RUN

    # Put display into waiting for a press mode.
    display.WriteToDisplay(GD.BACKLIGHT_OFF)
    display.DisplayForm(GD.BLANK_SCREEN_FORM)
    display.DisplayPressToStart()
    display.WriteToDisplay(GD.BACKLIGHT_LOW)

    # For android screen
    display.DisplayMiddleLeftInfoPrompt(GD.PRESS_ANY_KEY_PROMPT)

    # Clear any boost presses so they are not there when we restart later.
    GD.boostPresses = 0

    # Start a zones check to update any changes to zones.
    GD.checkZone = 0
def ProcessAutoManualKey(zone):

    # Switch between Auto and Manual mode with each press.
    zones.zoneTimes.SwitchModes()
    display.DisplayBottomLeftInfoPrompt()
    display.DisplayZoneMode(zone)

    return 1
def ProcessEnableDisableKey(zone):

    # Only allow disable if we are in days entry mode.
    if GD.currentMode == GD.MODE_PROG_DAYS_ON:
        # Do disable/enable (toggle action).
        zones.zoneTimes.ModifyDay(GD.DAYS_INDEX, GD.DAYS_DISABLED)
        # Update save prompt as we have changed the data.
        display.DisplayBottomLeftInfoPrompt()
        display.DisplayBottomRightInfoPrompt()
        # Display the Current programming data entry. Force a display update.
        display.DisplayProgEntry(0, forceUpdate=True)

    return 1
def ProcessClearKey(zone):

    #  global GD.clearPending

    if GD.currentMode == GD.MODE_PROG_ON_AT:

        # Clear the on time.
        zones.zoneTimes.ClearTime(GD.ON_TIME_INDEX)
        # Display the new time.
        display.DisplayProgOnTime()

    elif GD.currentMode == GD.MODE_PROG_OFF_AT:

        # Clear the off time.
        zones.zoneTimes.ClearTime(GD.OFF_TIME_INDEX)
        # Display the new time.
        display.DisplayProgOffTimeAndDays()

    elif GD.currentMode == GD.MODE_PROG_DAYS_ON and zones.zoneTimes.CheckIfDisabled(
            GD.DAYS_INDEX) == False:

        # Clear the days if we are not disabled.
        zones.zoneTimes.ClearDays(GD.DAYS_INDEX)
        # Display the new day info. Days info is displayed with the off time.
        display.DisplayProgOffTimeAndDays()

    elif GD.currentMode in (GD.MODE_PROG_TIME, GD.MODE_PROG_DAY):

        if GD.clearPending == True:
            GD.clearPending = False
            # Clear the on time
            zones.zoneTimes.ClearTime(GD.ON_TIME_INDEX)
            # Clear the off time
            zones.zoneTimes.ClearTime(GD.OFF_TIME_INDEX)
            # Clear the days
            zones.zoneTimes.ClearDays(GD.DAYS_INDEX)

            # Display the on time, off time and days.
            display.DisplayProgOnTime()
            display.DisplayProgOffTimeAndDays()

        else:
            GD.clearPending = True

    # Update info prompts now we have chaged the data
    display.DisplayBottomLeftInfoPrompt()
    display.DisplayBottomRightInfoPrompt()

    return 1
def ProcessWakeupKey(zone):

    # Start up the display by setting the correct form. Clear any info messages.
    display.DisplayForm(GD.MAIN_SCREEN_FORM)
    display.DisplayBottomLeftInfoPrompt(GD.BLANK_PROMPT)
    display.DisplayBottomRightInfoPrompt()

    # Check if the system is on or off. If we are on we display the rad select screen. If we are off we display the system screen.
    if system.systemControl[GD.SYSTEM_OFF_MODE].CheckIfBitHigh() == True:
        ProcessSystemKey(-1)
    else:
        ProcessRadKey(-1)

    display.WriteToDisplay(GD.BACKLIGHT_HALF)

    return 1
def ProcessUfhSelectExitKey(keyValue):

    # Return to ufh waiting select mode and keyboard.
    GD.currentMode = GD.MODE_UFH_WAITING_ZONE_SELECT
    display.DisplayKeyboardImage(useMode=GD.currentMode)

    # Clear any previously active select key and display any active bands.
    keyData.UpdateSelectKeyGroupText(textIdle=GD.KEY_GROUP_UFH)
    display.UpdateSelectKeyImages(GD.KEY_GROUP_UFH)

    # Update prompts.
    display.DisplayMiddleLeftInfoPrompt(GD.UFH_SELECT_PROMPT)
    display.DisplayBottomLeftInfoPrompt(GD.INFO_1_BLANKED)
    display.DisplayZoneMode()

    return 1
def ProcessDayOfWeekKey(key):

    # Only process key if we are in correct mode and not disabled.
    if GD.currentMode == GD.MODE_PROG_DAYS_ON and zones.zoneTimes.CheckIfDisabled(
            GD.DAYS_INDEX) == False:
        # Convert keycode to values 0 to 9 so we can use as index to string of days.
        key = key - 60

        # Update the day.
        zones.zoneTimes.ModifyDay(GD.DAYS_INDEX, key)
        # Display the new day info.
        display.DisplayProgOffTimeAndDays()

        # Update the save and valid prompts now we have changed the data.
        display.DisplayBottomLeftInfoPrompt()
        display.DisplayBottomRightInfoPrompt()

    return 1
def ProcessProgramKey(zone):

    # Switch to the program keyboard (time entry).
    display.DisplayKeyboardImage(GD.TIME_SELECT_KEYBOARD_IMAGE)

    # Say we are in programming mode. We start in time entry mode.
    GD.currentMode = GD.MODE_PROG_TIME

    # Clear the zone select data ready for displaying programming data fields.
    display.DisplayMiddleLeftInfoPrompt(GD.BLANK_PROMPT)

    # Read the programmed times and display the 1st programming data entry. Force a display update.
    zones.ReadZoneTimes(zone)
    display.DisplayProgEntry(1, forceUpdate=True)
    display.DisplayBottomLeftInfoPrompt()
    display.DisplayBottomRightInfoPrompt()

    return 1
def ProcessNewKey(zone):

    #Limit number of entries to a reasonable number.
    if zones.zoneTimes.GetNumberOfProgramEntries() < 8:
        # Create new entry and get the number.
        newEntryNumber = zones.zoneTimes.AddNewEntry()

        # Return to initial state if we are in ON AT, OFF AT or day select mode.
        if GD.currentMode in (GD.MODE_PROG_ON_AT, GD.MODE_PROG_OFF_AT,
                              GD.MODE_PROG_DAYS_ON):
            GD.currentMode = GD.MODE_PROG_DAY if GD.currentMode == GD.MODE_PROG_DAYS_ON else GD.MODE_PROG_TIME

        # Update info prompts now we have changed the data.
        display.DisplayBottomLeftInfoPrompt()
        display.DisplayBottomRightInfoPrompt()

        # Display the new programming data entry.
        display.DisplayProgEntry(newEntryNumber, forceUpdate=True)

    return 1
def ProcessSaveKey(zone):

    # Only save if a change has been made.
    if zones.zoneTimes.CheckIfDataChanged() == True:
        zones.zoneTimes.UpdateProgramEntries()

    # Return to initial state if we are in ON AT, OFF AT or day select mode.
    if GD.currentMode in (GD.MODE_PROG_ON_AT, GD.MODE_PROG_OFF_AT,
                          GD.MODE_PROG_DAYS_ON):
        GD.currentMode = GD.MODE_PROG_DAY if GD.currentMode == GD.MODE_PROG_DAYS_ON else GD.MODE_PROG_TIME

    # Clear the flag now we have saved data and update prompts.
    GD.exitPending = False
    display.DisplayBottomLeftInfoPrompt()
    display.DisplayBottomRightInfoPrompt()

    # We need to re-display as the current entry may be blank and will be removed on save. Go back to 1st entry.
    display.DisplayProgEntry(1, forceUpdate=True)

    return 1
def ProcessDaysKey(zone):

    #We only process key if we are NOT already in day edit mode
    if GD.currentMode != GD.MODE_PROG_DAYS_ON:

        # Say we are now in day edit mode.
        GD.currentMode = GD.MODE_PROG_DAYS_ON

        # Switch to the day entry program keyboard.
        display.DisplayKeyboardImage(GD.DAY_SELECT_KEYBOARD_IMAGE)

        # Check if any edit has been aborted and if it has restore the original data. No need to update
        # display as we do this below.
        CheckForEditAbort(updateDisplay=False)

        # Update info prompts.
        display.DisplayBottomLeftInfoPrompt()
        display.DisplayBottomRightInfoPrompt()

        # Display the Current programming data entry. Force a display update.
        display.DisplayProgEntry(0, forceUpdate=True)

    return 1
def ProcessKeys(keyValue, zone):

    # Dictionary holds keycodes with functions to call.
    keyList = {
        GD.KEYVALUE_DAY:
        ProcessDaysKey,
        GD.KEYVALUE_BOOST:
        ProcessBoostKey,
        GD.KEYVALUE_CAN_RES:
        ProcessCanResKey,
        GD.KEYVALUE_PROGRAM:
        ProcessProgramKey,
        GD.KEYVALUE_RAD:
        ProcessRadKey,
        GD.KEYVALUE_UFH:
        ProcessUfhKey,
        GD.KEYVALUE_NEXT_PROGRAM_ENTRY:
        ProcessNextProgramEntrytKey,
        GD.KEYVALUE_NEXT_STATUS_ENTRY:
        keysSystem.ProcessNextStatusEntrytKey,
        GD.KEYVALUE_PREV_PROGRAM_ENTRY:
        ProcessPreviousProgramEntrytKey,
        GD.KEYVALUE_PREV_STATUS_ENTRY:
        keysSystem.ProcessPreviousStatusEntrytKey,
        GD.KEYVALUE_ON_AT:
        ProcessOnAtKey,
        GD.KEYVALUE_OFF_AT:
        ProcessOffAtKey,
        GD.KEYVALUE_CLEAR:
        ProcessClearKey,
        GD.KEYVALUE_SAVE:
        ProcessSaveKey,
        GD.KEYVALUE_NEW:
        ProcessNewKey,
        GD.KEYVALUE_ENABLE_DISABLE:
        ProcessEnableDisableKey,
        GD.KEYVALUE_AUTO_MANUAL:
        ProcessAutoManualKey,
        GD.KEYVALUE_WAKEUP:
        ProcessWakeupKey,
        GD.KEYVALUE_SYSTEM:
        ProcessSystemKey,
        GD.KEYVALUE_FINISHED:
        ProcessFinishedKey,
        GD.KEYVALUE_SYSTEM_OFF:
        keysSystem.ProcessSystemModeKeys,
        GD.KEYVALUE_AUTO_MODE:
        keysSystem.ProcessSystemModeKeys,
        GD.KEYVALUE_MANUAL_MODE:
        keysSystem.ProcessSystemModeKeys,
        GD.KEYVALUE_HOLIDAY_MODE:
        keysSystem.ProcessSystemModeKeys,
        GD.KEYVALUE_SYSTEM_OPTIONS:
        keysSystem.ProcessSystemOptionsKey,
        GD.KEYVALUE_MANUAL_OPTIONS:
        keysSystem.ProcessManualOptionsKey,
        GD.KEYVALUE_AUTO_OPTIONS:
        keysSystem.ProcessAutoOptionsKey,
        GD.KEYVALUE_HOLIDAY_OPTIONS:
        keysSystem.ProcessHolidayOptionsKey,
        GD.KEYVALUE_IMMERSION_TIMES:
        keysSystem.ProcessImmersionTimesKey,
        GD.KEYVALUE_T1_TO_HEAT:
        keysSystem.ProcessT1ToHeatingKey,
        GD.KEYVALUE_T2_TO_HEAT:
        keysSystem.ProcessT2ToHeatingKey,
        GD.KEYVALUE_OIL_TO_HEAT:
        keysSystem.ProcessOilToHeatingKey,
        GD.KEYVALUE_OIL_TO_T1:
        keysSystem.ProcessOilToT1Key,
        GD.KEYVALUE_OIL_TO_T2:
        keysSystem.ProcessOilToT2Key,
        GD.KEYVALUE_OIL_OFF:
        keysSystem.ProcessOilOffKey,
        GD.KEYVALUE_MANUAL_OVERRIDE:
        keysSystem.ProcessManualOverrideKey,
        GD.KEYVALUE_DISPLAY_STATUS:
        keysSystem.ProcessDisplayStatusKey,
        GD.KEYVALUE_IMMERSION_CONTROL:
        keysSystem.ProcessManualOverrideKeySubMenu,
        GD.KEYVALUE_WOODBURNER_CONTROL:
        keysSystem.ProcessManualOverrideKeySubMenu,
        GD.KEYVALUE_TANK_1_CONTROL:
        keysSystem.ProcessManualOverrideKeySubMenu,
        GD.KEYVALUE_TANK_2_CONTROL:
        keysSystem.ProcessManualOverrideKeySubMenu,
        GD.KEYVALUE_HEATING_CONTROL:
        keysSystem.ProcessManualOverrideKeySubMenu,
        GD.KEYVALUE_BOILER_CONTROL:
        keysSystem.ProcessManualOverrideKeySubMenu,
        GD.KEYVALUE_EXIT:
        ProcessExitKey,
        GD.KEYVALUE_RAD_SELECT_EXIT:
        ProcessRadSelectExitKey,
        GD.KEYVALUE_UFH_SELECT_EXIT:
        ProcessUfhSelectExitKey,
        GD.KEYVALUE_EDIT_EXIT:
        ProcessEditExitKey,
        GD.KEYVALUE_RETURN_TO_SYSTEM_EXIT:
        ProcessSystemKey,
        GD.KEYVALUE_MANUAL_CONTROL_MAIN_MENU_EXIT:
        keysSystem.ProcessManualOptionsKey,
        GD.KEYVALUE_MANUAL_CONTROL_OPTION_EXIT:
        keysSystem.ProcessManualOverrideKey,
        GD.KEYVALUE_RETURN_TO_SYSTEM_OPTIONS_EXIT:
        keysSystem.ProcessSystemOptionsKey
    }

    # If we have a pending exit we need to check if the key is SAVE, EXIT or something else.
    if GD.exitPending == True and keyValue not in (GD.KEYVALUE_SAVE,
                                                   GD.KEYVALUE_EXIT):
        # Not SAVE or EXIT so clear pending status.
        GD.exitPending = False
        display.DisplayBottomLeftInfoPrompt()

    # If we have a pending clear we need to check if the key is CLEAR or something else.
    if GD.clearPending == True and keyValue != GD.KEYVALUE_CLEAR:
        # Not CLEAR so clear pending status.
        GD.clearPending = False
        display.DisplayBottomLeftInfoPrompt()

    # If the key is a general control type key then call the function for the supplied key.
    if keyValue in keyList:
        # If we are processing a system key we pass the key value to the function.
        # If it is a zone control or edit key we pass the current zone.
        passData = keyValue if keyValue >= GD.SYSTEM_KEY_START else zone
        status = keyList[keyValue](passData)

    # Is it a manual override key? These all call the same function.
    elif keyValue in (GD.KEY_GROUP_ALL_MANUAL_OVERRIDE):

        # At present imm 3 and imm 4 are in parallel so we need to activate both.
        #        if keyValue in (GD.KEYVALUE_IMM_3_ON, GD.KEYVALUE_IMM_4_ON) :
        #            status = keysSystem.ProcessManualOverrideOnOffKey (GD.KEYVALUE_IMM_3_ON)
        #            status = keysSystem.ProcessManualOverrideOnOffKey (GD.KEYVALUE_IMM_4_ON)
        #        elif keyValue in (GD.KEYVALUE_IMM_3_OFF, GD.KEYVALUE_IMM_4_OFF) :
        #            status = keysSystem.ProcessManualOverrideOnOffKey (GD.KEYVALUE_IMM_3_OFF)
        #            status = keysSystem.ProcessManualOverrideOnOffKey (GD.KEYVALUE_IMM_4_OFF)
        #        else :
        # Process key
        status = keysSystem.ProcessManualOverrideOnOffKey(keyValue)

    # Is it a rad select key?, these all call the same function.
    elif keyValue in GD.KEY_GROUP_RADS:
        status = ProcessRadSelectKey(keyValue)

    # Is it a ufh select key?, these all call the same function.
    elif keyValue in GD.KEY_GROUP_UFH:
        status = ProcessUfhSelectKey(keyValue)

    # Is it an immersion select key?, these all call the same function.
    elif keyValue in GD.KEY_GROUP_IMMERSIONS:
        status = keysSystem.ProcessImmersionSelectKey(keyValue)

    # Is it a numeric key?, these all call the same function.
    elif keyValue in GD.KEY_GROUP_NUMERIC:
        status = ProcessDigitKey(keyValue)

    # Is it a day key?, these all call the same function.
    elif keyValue in GD.KEY_GROUP_DAYS:
        status = ProcessDayOfWeekKey(keyValue)

    # Is it a status key?, these all call the same function.
    elif keyValue in GD.KEY_GROUP_STATUS:
        status = keysSystem.ProcessStatusKey(keyValue)

    return
def main():

    #Initialise the I2C port used for the relays and get I2C port object.
    I2CPort = smbus.SMBus(1)

    #Initialise 1 wire.
    ow.init('localhost:4304')

    # Create data structures to hold zone data.
    zones.InitialiseZones()

    # Initialise all the system control bits.
    system.InitialiseSystemControlBits()

    # Initialise the RS232 serial port and the display module and get serial port object.
    display.RS232Port = display.InitialiseDisplayKeyboardModule()

    # We run a process to read the 1 wire bus for the temperature sensors. Communication with the process is via 2 queues.
    # The command queue takes a 2 element tuple of the sensor id to start reading and the rad interval.
    # The result queue will contain a 2 element tuple of a sensor id and the temperature just read.
    # So here we initialise 2 queues and start the process.
    commandQueue = multiprocessing.Queue()
    resultQueue = multiprocessing.Queue()
    p = multiprocessing.Process(target=sensor.ReadTemperatures,
                                args=(
                                    commandQueue,
                                    resultQueue,
                                ))
    p.start()

    # Start reading the woodburner temperature sensors. We always have the woodburner code operational, even when the
    # system is in OFF mode. This is for safety lest the woodburner is lit with the system turned off.
    commandQueue.put((GD.SYSTEM_WOODBURNER_FLOW_TEMP, 1))
    commandQueue.put((GD.SYSTEM_WOODBURNER_RETURN_TEMP, 1))

    # DEBUG OUTPUT
    for entry in system.systemControl:
        print system.systemControl[entry].GetDisplayText(
        ), system.systemControl[entry].GetAddress()

    # This is the zone that is currently selected for viewing or editing. Initialise to the 1st zone (rad 1).
    selectedZone = GD.RAD_BED1

    # We keep the last zone selected so that we can determine if a new zone has been selected.
    lastSelectedZone = GD.NO_ZONE

    # We need to read a zone to create a time object to use later.
    zones.ReadZoneTimes(selectedZone)

    # We time how long since the user last pressed a key so that we can blank the screen on no key activity and
    # also start zone checking and relay operation, which is suspended while key activity is in operation.
    lastKeypressTime = time.time()
    lastKeyImage = ''

    # This is a flag for our 1 second threading timer. When the timer expires we set this flag to give us a 1 second tick.
    oneSecondTimerTick = threading.Event()
    # Set it so that we do the 1st 1 second operations.
    oneSecondTimerTick.set()

    # Use same startup screen as wakeup so simply call wakeup key code.
    keys.ProcessWakeupKey(-1)

    # Set volume on the display to max.
    display.AdjustVolume(GD.SOUND_VOLUME_MAX)

    # This is our main loop
    while (1):

        # Let the CPU have a break from us.
        time.sleep(0.01)

        # Check if we have received any temperature readings and if we have get and save them. We do this in the
        # main loop so we can empty the queue faster than the readTemperatures process can fill it.
        if not resultQueue.empty():
            systemId, sensorTemperature = resultQueue.get()
            system.systemControl[systemId].UpdateTemperature(sensorTemperature)
            print system.systemControl[systemId].GetDisplayText(
            ), sensorTemperature

        # Process possible key message from display module.
        keyCode = display.CheckForSerialInput(GD.CHECK_FOR_BUTTON)

        # Have we got a key? KeyCode is the physical button value on the display module
        if keyCode:
            # Sound key press.
            display.GenerateSound(GD.SOUND_CLICK)
            # Delay and then check for a key again. This will remove any key bounce that occurs in the delay time.
            # The delay also allows the sound to output without interference caused by sending data to the display
            # whilist it is generating the sound. We just ignore any keycode returned.
            time.sleep(0.4)
            display.CheckForSerialInput(GD.CHECK_FOR_BUTTON)

            # If we detect a longer keybounce (2 keypresses within a set time) ignore the key by making it zero.
            if time.time() < (lastKeypressTime + 0.85):
                print 'BOUNCE', time.time() - lastKeypressTime
                # Re-display the last key image again as we will have lost it on the bounce.
                display.WriteToDisplay(lastKeyImage)
                # Clear the key
                keyCode = 0
                keyValue = 0
            else:
                # Get the actual value for the key. Each physical key can have several different keyValues. It depends on the
                # mode we are in as we display different keyboards for each mode.
                keyValue = keyData.GetKeyValue(keyCode)
                print 'INITIAL KEYVALUE', keyValue
            # Found a key so reload our bounce timer.
            lastKeypressTime = time.time()

            # Have we got a key value? We may not have if it was a key bounce or a blank key.
            if keyValue:
                # We have a key so reload activity timer.
                GD.timeToRunMode = GD.KEYPRESS_WAIT_TIME

                # If it is a zone select key save it for later use.
                if keyValue in GD.KEY_GROUP_ALL_ZONES:
                    # Adjust key value as zones start at 0 and key is 1 for zone 0.
                    selectedZone = keyValue - 1

                    # If we have moved zones reset the boost key counter so we start at 1st boost for new zone.
                    if lastSelectedZone != selectedZone:
                        GD.boostPresses = 0
                        lastSelectedZone = selectedZone
                        print 'SELECTED ZONE', selectedZone

                # If it is status select key save it for later use.
                elif keyValue in GD.KEY_GROUP_STATUS:
                    selectedZone = keyValue

                # Go and process key.
                keys.ProcessKeys(keyValue, selectedZone)

                # We may now be in a different mode so we need to update the keyvalue for the new mode.
                keyValue = keyData.GetKeyValue(keyCode)

                # Update key image. Keep image so we can redisplay key after a key bounce.
                # If the key is displayed elsewhere lastKeyImage will be set null and the image will not be changed.
                lastKeyImage = keyData.GetKeyImageSequence(keyValue)
                display.WriteToDisplay(lastKeyImage)
                print 'KEYVALUE NOW', keyValue

                # If we are in a zone select mode read the programmed times and update display so user can see the zone status.
                if GD.currentMode in (GD.MODE_RAD_ZONE_SELECT,
                                      GD.MODE_UFH_ZONE_SELECT,
                                      GD.MODE_IMMERSION_SELECT):
                    zones.ReadZoneTimes(selectedZone)
                    zoneStatus = zones.UpdateZoneStatus(selectedZone)
                    display.DisplayZoneStatus(selectedZone, zoneStatus)
                    display.DisplayZoneMode(selectedZone)
                    display.DisplayBottomLeftInfoPrompt()

                    # Update boost key image and if it is the key currently pressed save the image.
                    tempKeyImage = display.DisplayBoostKeyImage(selectedZone)
                    if keyValue == GD.KEYVALUE_BOOST:
                        lastKeyImage = tempKeyImage

                    # Update cancel / resume key image and if it is the key currently pressed save the image.
                    tempKeyImage = display.DisplayCancelResumeKeyImage(
                        selectedZone)
                    if keyValue == GD.KEYVALUE_CAN_RES:
                        lastKeyImage = tempKeyImage

                # If we are in a  programming mode update the auto / manual / enable / disable key image and if it is
                # the key currently pressed save the image.
                if GD.currentMode in (GD.MODE_PROG_TIME, GD.MODE_PROG_ON_AT,
                                      GD.MODE_PROG_OFF_AT, GD.MODE_PROG_DAY,
                                      GD.MODE_PROG_DAYS_ON):

                    tempKeyImage = display.DisplayAutoEnableKeyImage(
                        selectedZone)
                    if keyValue in (GD.KEYVALUE_AUTO_MANUAL,
                                    GD.KEYVALUE_ENABLE_DISABLE):
                        lastKeyImage = tempKeyImage

        # If there has been no activity we will revert to run mode and display the 'press to start' screen.
        if GD.timeToRunMode == 0:
            # Make flag -ve so we don't come here again.
            GD.timeToRunMode -= 1

            # Set up run mode.
            SwitchToRunMode()

        # Has our 1 second timer ticked? If it has, do all the operations we do at 1 second intervals.
        # Restart the 1 second timer. It will set the oneSecondTimerTick event flag on timeout. This provides us with a 1 second tick.
        if oneSecondTimerTick.isSet():

            oneSecondTimerTick.clear()
            threading.Timer(1, OneSecondTimeout,
                            args=(oneSecondTimerTick, )).start()

            # Do our 1 second operations.
            ProcessOneSecondOperations(I2CPort, resultQueue)

        # Are we in run mode and a zone check is required. checkZone will have been set to 0 if a check is required
        if GD.currentMode == GD.MODE_RUN and GD.checkZone >= 0:

            # Update a zone's status.
            zones.UpdateZoneStatus(GD.checkZone)

            # Do any heating zone relay operation required. Heating zones are from 0 to 29. The relays for heating zones
            # are latching and in a matrix and are therefore accessed differently to system relays.
            if GD.checkZone < 30:
                relay.ActivateHeatingZoneRelay(I2CPort, GD.checkZone)

            # Immersion zones are from 30 to 33. Zones 30-33 are the 4 immersions. We will set the output control bits here and
            # the system relay code will control the actual outputs (relays).
            else:
                system.SetImmersionOutputControlBitsFromImmersionStatus(
                    GD.checkZone)

            # If a zone pump is required set bits so system relay code sets it on.
            system.SetZonePumpOutputControlBitsFromZonePumpStatus(GD.checkZone)

            # Update the band status for a heating or immersion zone.
            keyData.SetZoneSelectBandStatus(GD.checkZone)

            # Move to next zone to check. When we pass the last zone stop further checking.
            GD.checkZone += 1
            if GD.checkZone >= 34:
                GD.checkZone = -1

        # Not in run mode. Are we in a select mode where a zone's state is displayed by a band?
        elif GD.currentMode in (GD.MODE_RAD_WAITING_ZONE_SELECT,
                                GD.MODE_RAD_ZONE_SELECT,
                                GD.MODE_IMMERSION_WAITING_SELECT,
                                GD.MODE_IMMERSION_SELECT,
                                GD.MODE_UFH_WAITING_ZONE_SELECT,
                                GD.MODE_UFH_ZONE_SELECT):

            # If this is the 1st time here since run mode (checkZone = -1) or we have reached the end we need to set checkZone
            # to zero, otherwise move to next zone.
            if GD.checkZone < 0 or GD.checkZone >= 33:
                GD.checkZone = 0
            else:
                GD.checkZone += 1

            # Update the band status for a heating or immersion zone.
            keyData.SetZoneSelectBandStatus(GD.checkZone)