"""
Name: console_task
Title: Console Task
Author: Cooper
Date: 04/05/2020
Modified: 04/01/2021 (Happy new year!)

Desc:  Seeing how everyone wants to ship a DG3 with a system despite one not being specified, it always turns out that
the DG3 is there for manual mode backup.  Instead of applying the same bunch of commands to all the protocols, this
will be the one stop shop for all console related stuff such as:
- Production mode
- Polling console for things like destcode, routecode, test status
- Polling console for USB things
- Polling sign mapping
- Polling sign data from the console's copy of the database (This is the new method going forwards)
- Polling console parameters
- Handling WDM, anything that uses this module will get WDM for free!
- Updating eric.bins obtained remotely

Potentially:
- Dealing with terminal mode
- 3rd Party FTP, although would be tricky

Removed:
- (To be) Support for eric.bins and eric.jsons via xfer

"""
import os
import queue
import struct
from queue import Queue
import threading
import time
import _thread
import logging

from hanip.onionip import hano1
from hanip.onionip import xferFileHandler

class ConsoleTask(object):
    def __init__(self, config_dict, hw_dict, config_dir, data_dir):
        self.config_dict = config_dict
        self.hw_dict = hw_dict
        self.config_dir = config_dir
        self.data_dir = data_dir

        self.stop = False

        # Start the required console interface
        try:
            self.init_g4()
        except ImportError:
            self.init_hano1()

        """
        Functionality variables for hanover_mqtt SR2378 (Do not touch this for other modules)
        """
        self.dest_code = "0000000000"
        self.route_code = "0000"
        self.info_code = "00"
        self.remote_route = "0000"
        self.remote_info = "00"

        self.key_presses = Queue(maxsize=8)
        self.KEY_FLAG = False
        self.monitor_console_flag = False

        self.des_len = 4
        self.route_len = 4
        self.info_len = 2

        self.console_updating = False       #It seems nothing used this so now used for WDM
        self.disable_test_mode = False
        self.data_version = self.hano.obtain_data_version()

        self.parent_done = threading.Event()
        self.work_done = threading.Event()
        self.parent_done.set()
        self.work_done.clear()

        """
        Console Variables
        """
        self.delay_interval = 1
        self.terminal_mode = False
        self.reboot_required = False

        self.console_version = None
        self.manual_code = "0000000000" #Code that is set via the front panel
        self.remote_code = "0000000000" #Code that is set via 3rd party e.g. OBC
        self.new_remote_code = False
        self.remote_set_code = False #Flag to tell other portions of the application how the current code is set
        self.remote_code_valid = False
        self.destination_status = "2"
        self.test_mode = None
        self.remote_test_mode = False
        self.remote_message = None
        self.console_poll_interval = 1
        self.console_active = True
        self.digital_input_values = [0, 0, 0]

        self.console_parameters = {}

        """
        Sign Variables
        """
        self.first_page_timer = 1       #This is the T0 parameter from the console
        self.subsequent_page_timer = 1  #This is the T1 parameter from the console
        self.sign_mapping_table = None
        self.display_message = None
        self.sign_messages = None       #This is a list containing all the obtained sign data
        self.sign_data_poll_interval = 1  # This is the time between each data poll, do we use this?

        """
        WDM/Cloud Update Variables
        """
        self.wdm_enable = self.check_if_wdm_enabled()
        self.using_config_cloud_settings = False
        self.cloud_scheme = "1"     # This is the flag to set which cloud scheme to use, 1=old, 2=new
        self.wdm_mqtt_connected = False
        self.wdm_downloads_enabled = False
        self.wdm_parameter_timer = 0
        self.wdm_sign_firmware_poll_timer = 0
        self.ctrl_loading = None
        self.wdm_forced_update_requested = False

        """
        3rd Party Update Variables
        """
        # Update status needs to be defined it is not being used by WDM, but for 3rd party update modules
        self.data_update_available = False
        self.data_update_filename = None
        self.data_update_status = "NTP"
        self.data_update_statues = {
            "NTP": "Nothing to process",
            "WTP": "Waiting to process",
            "PROC": "Processing",
            "XFER": "Transferring",
            "XFERD": "Transferred",
            "DONE": "Complete",
            "FAIL": "Failed",
            "ERR_UZ": "Unzip error",
            "ERR_CP": "Copy failed",
            "ERR_XFER": "Transfer failed",
            "ERR_NH": "Not Halal",
        }

        self.update_flags = {
            "eric": False,
            "config": False,
            "fontlib": False,
        }

        self.xfer_handler = xferFileHandler.XferFileHandler(self.config_dir, self.data_dir)
        self.obtain_other_parameters()
        self.init_wdm()
        self.init_wdm_mqtt_cred_publisher()
        self.get_code_lengths()

    """
    ###################################################################################################################
    Other console stuff
    """
    def init_hano1(self):
        logging.info("HANO: Initialising")
        comport = self.config_dict["SERIAL_unix_comport"]
        baud = self.config_dict["SERIAL_baudrate_host_console"]

        # This module should give control of the serial port to the hano1 module
        self.hano = hano1.HANO1(comport, baud, True, self.data_dir)

    def init_g4(self):
        # Attempt to connect to the g4 console application.
        from __main__ import G4_CONSOLE
        logging.info("G4: Initialising")
        self.hano = G4_CONSOLE()

    def reboot_console(self, wait=False):
        """
        This sends the reboot command to the console
        """
        self.hano.rebootConsole()

        if wait:
            self.wait_for_console_to_boot()


    def wait_for_console_to_boot(self, time_to_wait=30):
        """
        This just sits in a loop and waits for the console to reboot, default is 30 seconds
        """
        for x in range(time_to_wait):
            if len(self.hano.transmitMessage("a", True, 30)) > 0:
                break
            time.sleep(1)

        logging.debug("CT: Purging serial port just incase console is confused")
        time.sleep(1)
        self.hano.readSerial(100)

    def co_thread_handler(self, ready):
        """Communicates thread status to a calling
        parent thread. Set True to inform parent it
        should wait, and False to inform parent this
        thread as completed its work.

        :param ready: True/False to inform parent state
        """
        if ready:
            while not self.parent_done.wait(3):
                pass
            self.work_done.clear()
        elif not ready:
            self.work_done.set()


    """
    ###################################################################################################################
    Console IO
    """
    def show_remote_message(self, text, line, time):
        """
        Allows another module to set a message on the screen but this only sets the variable and doesnt actually do the
        displaying
        """
        # logging.info("CT: New remote screen message")
        if text == None:
            self.remote_message = None
        else:
            self.remote_message = (text, line, time)

    def update_console_display(self, text, line, time):
        self.hano.showOnConsole(text, line, time)

    def poll_console(self):
        logging.info("CT: Polling console...")
        status, keyspressed = self.hano.poll_console()

        if status == 0:
            #When a USB payload has been sucessfully received
            self.process_payload()
        elif status == 1:
            #When an invalid reply, failed payload or NAK
            logging.info("CT: Nothing to process from console")
            pass
        elif status == 2:
            #Terminal mode without lock code
            self.show_terminal_status(admin=False)
        elif status == 4:
            # Terminal mode after lock code
            self.show_terminal_status(admin=True)
        elif keyspressed is not None:
            self.obtain_key_press(keyspressed)

        else:
            logging.error("CT: Invalid reply")

    def get_console_digital_inputs(self):
        """
        Obtains the states of the consoles digital inputs
        """
        states = self.hano.get_digital_inputs()
        self.digital_input_values = [0, 0, 0]

        for input, state in enumerate(states):
            try:
                if state == "1":
                    self.digital_input_values[input] = 1
            except IndexError:
                pass


    """
    ###################################################################################################################
    Console Terminal Mode
    """

    def show_terminal_status_old(self):
        """
        Retain this for now, this is the old terminal mode which I think ISI mode still uses
        :return:
        """
        self.hano.clear_terminal()
        self.hano.show_on_terminal("0", "L", "Onion ver: %s %s" %
                                    (self.hw_dict["onion_ver"], self.config_dict["MODE_service_mode"]))
        self.hano.show_on_terminal("1", "L", "Host ver: %s" % self.hw_dict["software_version"])
        self.hano.show_on_terminal("2", "L", "IP: %s" % self.hw_dict["unit_IP"])

        time.sleep(5)

    def get_terminal_menu(self, admin=False):
        """
        Stores the various menus used in terminal mode
        :return:
        """
        button_labels_yes = "Yes"
        button_labels_yes_no = "Yes     No"
        button_labels_yes_back = "Yes                 Back"
        button_labels_yes_up_down_exit = "Yes                     Up      Down     Exit"

        menu_items = {
            "page_1": {
                "id": None,
                "line1": ("0", "L", "Onion ver: %s" % self.hw_dict["onion_ver"]),
                "line2": ("1", "L", "Mode: %s" % self.config_dict["MODE_service_mode"]),
                "line3": ("2", "L", "Host ver: %s" % self.hw_dict["software_version"]),
            },
            "page_2": {
                "id": None,
                "line1": ("0", "L", "IP: %s" % self.hw_dict["unit_IP"]),
                "line2": ("1", "L", "MAC: %s" % self.hw_dict["unit_MAC"]),
                "line3": ("2", "L", "Serial: %s" % self.hw_dict["serial_number"]),
            },
            "page_3": {
                "id": None,
                "line1": ("0", "L", "DHCP Server: %s" % self.config_dict.get("NETWORK_dhcp_server", "Undef.")),
                "line2": ("1", "L", "DHCP Client: %s" % self.config_dict.get("NETWORK_dhcp_client", "Undef.")),
                "line3": ("2", "L", "Link local: %s" % self.config_dict.get("NETWORK_local_link_address", "Undef.")),
            },
            "page_4": {
                "id": None,
                "line1": ("0", "L", "WDM enabled: %s" % self.wdm_enable),
                "line2": ("1", "L", "Check NETWORK for WDM"),
                "line3": ("2", "L", "Settings"),
            },
            "page_5": {
                "id": "reboot",
                "line1": ("0", "L", "Reboot Onion?"),
                "line2": None,
                "line3": ("2", "L", button_labels_yes_back)
            },
        }

        if admin:
            #If the Onion terminal is accessed behind the lock code this allows to add menu options
            menu_items["page_6"] = {
                "id": "config",
                "line1": ("0", "L", "Delete config.cfg?"),
                "line2": ("1", "C", "Will revert settings!"),
                "line3": ("2", "L", button_labels_yes_back)
            }

        return menu_items

    def show_terminal_status(self, admin=False):
        """
        Terminal mode but with added interactivity!  Woohoo.

        :return:
        """
        menu_items = self.get_terminal_menu(admin)

        total_levels = len(menu_items.keys())
        level = 1

        while 1:
            # Keypress part
            self.co_thread_handler(True)
            keypress = self.hano.wait_for_keypress(1)
            logging.debug(keypress)

            if keypress == "+":
                if level == total_levels:
                    level = 1
                else:
                    level += 1
                self.hano.clear_terminal()
            elif keypress == "-":
                if level == 1:
                    level = total_levels
                else:
                    level -= 1
                self.hano.clear_terminal()
            elif keypress == "\x11" or keypress == "C":
                break
            else:
                mapped_key_value = self.dg3_eg3_keymapper(keypress)

                if mapped_key_value == 1:
                    break
                elif mapped_key_value == 2:
                    if menu_items["page_%s" % level]["id"] == "config":
                        self.handle_configuration_change(network_flag=True, config=True)
                        break
                    elif menu_items["page_%s" % level]["id"] == "reboot":
                        self.handle_configuration_change(network_flag=True, config=False)
                        break
                    else:
                        pass
                else:
                    pass

            #Display part
            try:
                menu_item = menu_items["page_%s" % level]
            except KeyError:
                self.hano.clear_terminal()
                self.hano.show_on_terminal("0", "L", "Menu error...")
                time.sleep(1)
                break
            else:
                for key, value in menu_item.items():
                    if value != None:
                        self.hano.show_on_terminal(value[0], value[1], value[2])
            self.co_thread_handler(False)

        self.hano.end_terminal_mode()

    def handle_configuration_change(self, network_flag=False, config=False):
        """
        This routine deals with menu options that have a "Yes" option.  At the moment there are options to:
        - Delete the network flag and reboot the onion
        - Delete the config and network flag and reboot the onion
        :param option:
        :return:
        """
        if config:
            try:
                os.remove(os.path.join(self.config_dir, "config.cfg"))
                self.hano.show_on_terminal("1", "L", "Deleting config.cfg")
            except OSError:
                pass

        if network_flag:
            try:
                os.remove(os.path.join(self.config_dir, "network.done"))
                self.hano.show_on_terminal("1", "L", "Deleting network.done")
            except OSError:
                pass

        self.hano.show_on_terminal("1", "L", "Restarting application...")
        self.reboot_required = True
        self.update_flags["config"] = True


    def dg3_eg3_keymapper(self, key):
        """
        Annoyingly if using the screen to show button function, the left most button on DG3 is F/E whereas on EG3 is <
        So this function allows us to map without making the show_terminal_status() messy.
        :return: 1 to return, 2 for confirm, 0 everything else
        """

        console_type = self.hw_dict["model"].lower()

        if "dg3" in console_type:
            if key == "<":
                return 1
            elif key == "\x0D":
                return 2

        elif "eg3" in console_type:
            if key == "F":
                return 1
            elif key == "\x0D" or key == "<":
                return 2

        return 0

    def show_terminal_custom(self, listofstuff):
        self.hano.clear_terminal()

        for line, stuff in enumerate(listofstuff):
            self.hano.show_on_terminal(str(line), "L", stuff[line])

            if line == 2:
                #Can't show more than three lines at the moment!!
                break

        time.sleep(5)
        self.hano.end_terminal_mode()

    def obtain_key_press(self, key_presses):
        """
        Obtains the keypresses held by the console

        Queue.put is a blocking call by default if the queue is full, by setting
        block=False it will instead raise an exception.

        Any extra presses will be dropped because whatever is consuming the keypresses is not
        using them.  Perhaps it might be an idea to clear the buffer as the keypresses can be potentially
        very old by the time they are used?
        :return: key_presses:   key presses held by the console
        """
        logging.debug("CT: %s" % (key_presses))
        if key_presses is not None:
            for key in key_presses:
                try:
                    self.key_presses.put(key, block=False, timeout=0)
                except queue.Full:
                    logging.debug("CT: Keypress Queue full, dropping extra presses")
                    break

    """
    ###################################################################################################################
    WDM Handling
    """

    def init_wdm(self):
        """
        Initialises the WDM client
        """
        if self.wdm_enable:
            logging.info("CT: Starting WDM")
            wdm_parameters = self.obtain_wdm_parameters()
            _wdm_parameters = wdm_parameters.copy()
            _wdm_parameters["password"] = "Top Secret ;)"
            logging.debug(_wdm_parameters)
            self.wdm_parameter_timer = time.time()

            from hanip.hanover_cloud import wdm_client
            self.wdm = wdm_client.WDMClient(self.hw_dict, self.data_dir)
            self.wdm.update_wdm_configurations(wdm_parameters)
            self.wdm.setup_ftp_client()
            self.set_wdm_payload_attributes()
            self.wdm.allow_download = False

            _thread.start_new_thread(self.wdm.run, ())
        else:
            logging.info("CT: WDM Disabled")

    def check_if_wdm_enabled(self):
        """
        Seeing as there are two ways to enable this now, via the onion config or via the config.ini
        """
        if self.config_dict.get("WDM_enable", False):
            logging.info("CT: WDM Enabled via Onion config")
            return True
        else:
            enable = self.hano.get_parameter_value("ONION_WDM_ENABLE")
            if enable == None:
                return False
            elif enable.lower() == "true":
                logging.info("CT: WDM Enabled via Console config")
                return True
            else:
                return False

    def init_wdm_mqtt_cred_publisher(self):
        """
        Sets up the MQTT client to be able to transmit WDM credentials securely to the signs
        """
        if self.wdm_enable:
            logging.info("CT: Setting up secure MQTT channel")
            from hanip.hanover_cloud import wdm_credential_service
            self.wdm_cred_serv = wdm_credential_service.WDM_Credential_Service_Console(self.config_dict, self.hw_dict)
            status = self.wdm_cred_serv.connect_to_broker("127.0.0.1")

            if status:
                self.wdm_mqtt_connected = True
                # Only register if connection to own broker is successful, otherwise btoker is not setup properly
                self.wdm_cred_serv.register_service(self.hw_dict["unit_IP"], "Han-cloud-service")

    def publish_wdm_creds(self, credentials):
        """
        Publishes the credentials to the broker with the retain flag so that the sign will get it the moment it connects
        to the broker
        """
        try:
            self.wdm_cred_serv.publish_wdm_creds(credentials)
        except AttributeError:
            #This can be called before its setup due to where its called...
            pass


    def set_wdm_payload_attributes(self):
        """
        This tells the WDM module what sort of payloads to obtain along with all the necessary bits of information
        that it needs to process the payloads
        """
        if self.cloud_scheme == "1":
            cloud_paths = {
                "status_path": r"/vehicle/<UNIT_ID>/log",
                "token_root_path": r"/vehicle/<UNIT_ID>"
            }
        else:
            device = self.hw_dict["model"]
            device_path = device.upper()

            cloud_paths = {
                "status_path": r"/vehicle/<UNIT_ID>/<PATH>/log".replace("<PATH>", device_path),
                "token_root_path": r"/vehicle/<UNIT_ID>/<PATH>".replace("<PATH>", device_path)
            }


        payload_types = {
            "onion_config": {
                "token": "ONION_CONFIG_FILE",
                "file_name": "config.cfg",
                "file_local_path": "/etc/hanip/config.cfg",
                "transfer": False
            },
            "fontlib": {
                "token": "FONTLIB_FILE",
                "file_name": "fontlib.bin",
                "file_local_path": "/usr/share/renderbox/fontlib.bin",
                "transfer": False
            },
            "console_config": {
                "token": "CONSOLE_CONFIG_FILE",
                "file_name": "config.ini",
                "file_local_path": None,
                "transfer": True
            },
            "console_firmware": {
                "token": "CONSOLE_FIRMWARE_FILE",
                "file_name": "firmware.han",
                "file_local_path": None,
                "transfer": True
            },
            "destination_list": {
                "token": "DB_FILE",
                "file_name": "eric.bin",
                "file_local_path": None,
                "transfer": True,
                "check_size": True
            },
            "onion_firmware": {
                "token": "ONION_FIRMWARE_FILE",
                "file_name": "hanip_update.whl",
                "file_local_path": None,
                "transfer": True        # This is an exception technically nothing to transfer but needs processing differently
            },
        }

        self.wdm.set_payload_attributes(payload_types)
        self.wdm.set_cloud_paths(cloud_paths)

    def obtain_wdm_parameters(self):
        """
        WDM parameters will not be stored in the Onions configuration file.  The reason is so that we do not have to
        generate several configs where the only difference is the unit_id.

        The parameters are stored on the console which are either set via the front panel, or the config.ini.

        Some of these parameters will also need to be transferred onto the signs, so a smaller subset would need to be made
        """
        #The other parameters are needed for the wdm_client but this is needed for console task
        ctrl_loading_raw = self.hano.get_parameter_value("CTRL_LOADING_ROUTEDEST")
        self.ctrl_loading = self.parse_ctrl_loading(ctrl_loading_raw)

        cloud_scheme_value = self.hano.get_parameter_value("FTP_WIFI_MAN")
        if cloud_scheme_value is None:
            self.cloud_scheme = "1"
        else:
            self.cloud_scheme = cloud_scheme_value

        #Todo check server details valid
        # try:
        #     server_ip = urlparse(self.hano.get_parameter_value("FTP_SERVER_IP"))
        # except AttributeError:
        #     logging.warning("CT: Invalid server url/ip")

        wdm_parameters = {
            "unit_id": self.hano.get_parameter_value("UNIT_ID"),
            "username": self.hano.get_parameter_value("FTP_SERVER_USER_NAME"),
            "password": self.hano.get_parameter_value("FTP_SERVER_PASSWORD"),
            "server_ip": self.hano.get_parameter_value("FTP_SERVER_IP").lower(),
            "server_port": self.hano.get_parameter_value("FTP_PORT"),
            "holdoff": self.hano.get_parameter_value("FTP_SERVER_DELAY_WAIT"),
            "enable_tls": self.config_dict.get("WDM_enable_tls", False),
            "updates_permitted": self.wdm_downloads_enabled,
            "scheme": self.cloud_scheme
        }

        # Below is all the stuff needed for the status but not the client itself
        console_network_params = {
            "dhcp_server": self.config_dict.get("NETWORK_dhcp_server", "0.0.0.0"),
            "dhcp_client": self.config_dict.get("NETWORK_dhcp_client", "0.0.0.0"),
            "ip_address": self.hw_dict["unit_IP"],
            "ip_address_configured": self.config_dict.get("NETWORK_static_ip", "0.0.0.0."),
            "subnet_mask": self.config_dict.get("NETWORK_subnet_mask", "255.255.255.0"),
            "gateway": self.config_dict.get("NETWORK_gateway", "0.0.0.0"),
            "dns_server": self.config_dict.get("NETWORK_dns_server", "0.0.0.0"),
            "controller_profile": self.console_parameters.get("PF", "?").strip()
        }

        # print(wdm_parameters)
        self.publish_wdm_creds(wdm_parameters)
        #Shove the two dicts together
        wdm_parameters.update(console_network_params)
        return wdm_parameters

    def parse_ctrl_loading(self, ctrl_loading_raw: str) -> list:
        """
        This parses the ctrl loading string from the controller into something a bit more directly usable
        """
        logging.info("CT: Parsing CTRL_LOADING parameters")

        ctrl_loading_codes = []
        route_dest_pairs = ctrl_loading_raw.split(",")

        for pair in route_dest_pairs:
            any_route = False
            any_dest = False
            route_dest = pair.split("|")
            try:
                route = route_dest[0]
                dest = route_dest[1]

                if route == "-1":
                    any_route = True
                    route = "*"
                if dest == "-1":
                    any_dest = True
                    dest = "*"
            except IndexError:
                continue

            if any_route and any_dest:
                ctrl_loading_codes = ["*"]
                break

            if len(dest) > 4:
                #If dest code is longer than 4 digits then the route number does not matter.
                code = dest.zfill(8)
            else:
                code = (route.zfill(4) if route != "*" else route) + (dest.zfill(4) if route != "*" else dest)

            ctrl_loading_codes.append(code)

        logging.debug("CT: CTRL_LOADING values  " + ",".join(ctrl_loading_codes))
        return ctrl_loading_codes

    def check_ctrl_loading(self) -> int:
        """
        This checks the current codes against the CTRL loading values.

        Typical value looks like this:
        route|dest -> -1|-1

        -1 implies any route/dest, there can be up to ten pairs separated by a comma, but realistically is this used?
        """
        if self.ctrl_loading is None or self.ctrl_loading == ["*"]:
            logging.info("CT: CTRL_LOADING: Code matches")
            self.wdm_downloads_enabled = True
            return True

        #Grab whatever code is currentely set but omit the first two digits as they are reserved
        if self.remote_set_code:
            current_code = self.remote_code[2:]
        else:
            current_code = self.manual_code[2:]

        #As destcodes have variable lengths, it is assumed that the programmed in CTRL_LOADING codes have the correct
        #lengths too
        match = False

        for ctrl_loading in self.ctrl_loading:
            if current_code == ctrl_loading:
                logging.info("CT: CTRL_LOADING code exact match")
                match = True
                break
            elif ctrl_loading[0] == "*":
                if current_code.endswith(ctrl_loading.lstrip("*")):
                    logging.info("CT: CTRL_LOADING dest match")
                    match = True
                    break
            elif ctrl_loading[-1] == "*":
                if current_code.startswith(ctrl_loading.rstrip("*")):
                    logging.info("CT: CTRL_LOADING route match")
                    match = True
                    break

        if match:
            self.wdm_downloads_enabled = True
            return True
        else:
            logging.info("CT: CTRL_LOADING geen match")
            self.wdm_downloads_enabled = False
            return False

    def check_wdm(self):
        """
        This is the entry point into the WDM module be it checking whether there is a payload, and updating the console
        if there is one.  This also checks whether the currently set destination code permits an update via the
        CTRL_LOADING parameter.  There will be a bit of a delay from the moment WDM is permitted to download to when
        downloads are ready to be installed (provided there are some) because it will have to go through the process to
        obtain files.

        If the state changes between WDM cycles, then the files will be ready to load but won't be installed.

        This will also get the health of the signs provided by the console and provide it to the WDM module.
        :return:
        """
        updates_permitted = self.check_ctrl_loading()

        if updates_permitted:
            self.wdm.allow_download = True
            wdm_update_flags = self.wdm.get_importer_task_flags()

            if wdm_update_flags is not None:
                logging.info("CT: WDM updates available")
                self.console_updating = True
                self.process_wdm_updates(wdm_update_flags)
            else:
                logging.info("CT: No WDM updates available")
                self.console_updating = False

        else:
            self.wdm.allow_download = False
            logging.info("CT: CTRL_LOADING blocking")

        #Allow the forced installation of database regardless of what CTRL LOADING says
        # Only look for this if there are no updates left to process because there maybe some remaining
        if not self.console_updating:
            self.check_database_installed()

        if time.time() - self.wdm_parameter_timer > 10:
            logging.info("CT: Updating WDM parameters")
            self.wdm.update_wdm_configurations(self.obtain_wdm_parameters())
            self.wdm_parameter_timer = time.time()

        #Get sign status'  There should be an option to switch this on... or clever enough to decide...
        if self.hano.get_parameter_value("P0") == "SIGN":
            logging.info("CT: Obtaining sign statuses")
            self.wdm.sign_statuses = self.hano.obtain_sign_statuses()

            # This addition is really slowing things down so we need to limit it instead of doing it every cycle, it is
            # only carried out twice, once at the very start, and the second time after a default wait of 60 second
            # or after a configured time.
            if self.wdm_sign_firmware_poll_timer != -1:
                sign_firmware_poll_threshold = self.config_dict.get("CONSOLE_sign_firmware_poll_timer", 60)

                if self.wdm_sign_firmware_poll_timer == 0:
                    self.wdm.sign_firmwares = self.obtain_sign_firmwares()
                    self.wdm_sign_firmware_poll_timer = time.time()
                elif time.time() - self.wdm_sign_firmware_poll_timer > sign_firmware_poll_threshold:
                    self.wdm.sign_firmwares = self.obtain_sign_firmwares()
                    # Stop it polling again once the second one has happened.
                    self.wdm_sign_firmware_poll_timer = -1

    def process_wdm_updates(self, update_flags):
        """
        This deals with each payload and loads each payload sequentially, unfortunately it is not possible to send a
        complete blob for the console to deal with so they must be sent in a specific order:
        1) Firmware
        2) Config
        3) Database

        It is assumed that update flags are supplied in the correct order in which to load files.  This is defined by
        self.set_wdm_payload_attributes().
        """
        reboot_required = False

        # print(update_flags)

        for payload_type, payload_details in update_flags.copy().items():
            payload_path = os.path.join("/tmp", payload_details["name"])

            #Deal with console config and console firmware only
            if payload_type in ["console_config", "console_firmware"]:
                if payload_details["ready"] and payload_details["loaded"] is False:
                    logging.info("CT: Processing %s" % payload_type)

                    if self.send_to_console(payload_type, payload_path):
                        self.wdm.update_importer_task_flag(payload_type, True)

            #Deal with database file here only, as the flags from WDM have not been set appropriately even though the
            #file has been obtained
            if payload_type == "destination_list":
                #check_pass is empty when the size check hasnt been carried out yet.
                if payload_details["ready"] and payload_details["check_pass"] == "":
                    if self.check_available_space(payload_path):
                        #Tell WDM client to set GOT and LOAD flags
                        self.wdm.update_importer_database_size_check_flag(payload_type, True)
                        if self.send_to_console(payload_type, payload_path):
                            self.wdm.update_importer_task_flag(payload_type, True)
                    else:
                        #Tell WDM to set GET RESULT=1 flag
                        self.wdm.update_importer_database_size_check_flag(payload_type, False)

            if payload_type in ["console_config", "onion_config"]:
                if payload_details["ready"] and payload_details["loaded"]:
                    logging.info("CT: New config received: %s" % payload_type)
                    self.delete_network_flag()
                    reboot_required = True

            # This should be identical on sign task along with handle_onion_firmware_payload()
            if payload_type == "onion_firmware":
                if payload_details["ready"] and payload_details["loaded"] is False:
                    if self.handle_onion_firmware_payload():
                        self.wdm.update_importer_task_flag(payload_type, True)
                elif payload_details["ready"] and payload_details["loaded"]:
                    reboot_required = True

        if self.wdm.get_wdm_status() == 1:
            self.reset_wdm_state_machine()
            self.reboot_required = reboot_required

            if reboot_required:
                logging.info("CT: Rebooting console, please wait")
                self.hano.rebootConsole()
                self.wait_for_console_to_boot()

    def handle_onion_firmware_payload(self):
        """
        Between this and OAU there are a couple of flags that are taken into account so that both sides know what is
        going on.

        OAU will not apply an update without a trigger file, this trigger file also instructs OAU not to reboot the
        application either so it will sit and wait until it can continue.

        Before the application restarts, the oau_done_flag must be cleared.
        """
        oau_trigger_path = "/tmp/wdm_trigger"
        oau_done_flag = "/tmp/OAU_FIRMWARE_COMPLETE"
        update_done = False

        oau_trigger_exists = os.path.isfile(oau_trigger_path)
        oau_done_exists = os.path.isfile(oau_done_flag)

        if not oau_trigger_exists and not oau_done_exists:
            # If neither flags are present then set the trigger
            with open(oau_trigger_path, "w") as trigger:
                pass
        elif oau_trigger_exists and not oau_done_exists:
            # Trigger is there, just waiting for OAU to finish
            logging.info("CT: Waiting for OAU")

        elif oau_done_exists:
            # OA has finished
            logging.info("CT: OAU reports update complete")
            try:
                os.remove(oau_done_flag)
            except OSError:
                logging.warning("CT: Cannot remove OAU done flag")

            update_done = True

        return update_done


    def send_to_console(self, payload_type, payload_path):
        """
        This sends file to the console, was taken out of the subroutine above due to the database needing special
        treatment.
        """
        retries = 3

        for retry in range(retries):
            self.update_console_display("Transferring %s" % payload_type, 0, 3)
            status = self.transfer_to_console(payload_path, payload_type)

            logging.info("CT: Waiting for console to reboot")
            self.wait_for_console_to_boot()
            logging.debug("CT: Transfer status %s" % status)

            if status == 0:
                self.wdm.update_importer_task_flag(payload_type, True)
                return True
            else:
                logging.warning("CT: Transfer failed")
                time.sleep(0)

        return False


    def check_database_installed(self):
        """
        This checks if a database is needed to be installed usually when a firmware update has been completed
        """
        logging.debug("CT:Dest Status: %s Forced updReq: %s" % (self.destination_status, self.wdm_forced_update_requested))

        if not self.wdm_forced_update_requested:
            if self.destination_status == "-":
                logging.debug("CT: Hmm, theres no database")
                self.wdm.set_forced_update(["destination_list"])
                self.wdm_forced_update_requested = True

        else:
            if self.wdm.forced_update_ready is None:
                return
            else:
                try:
                    ready = self.wdm.forced_update_ready["destination_list"]["ready"]
                    too_big = self.wdm.forced_update_ready["destination_list"]["too_big"]
                except KeyError:
                    return

            if ready and not too_big:
                payload_path = os.path.join("/tmp", "eric.bin")
                if os.path.isfile(payload_path):
                    if self.check_available_space(payload_path):
                        self.update_console_display("Transferring %s" % "Database", 0, 0)
                        status = self.transfer_to_console(payload_path, "destination_list")

                        if not status:
                            logging.info("CT: Database forced installed")
                            self.wdm_forced_update_requested = False
                    else:
                        logging.info("CT: Database cannot be forced too phat.")
                        self.wdm.set_destination_list_too_phat(True)
                        self.wdm_forced_update_requested = False

            elif ready and too_big:
                #If checks already prove too big, then no point in doing anything
                self.wdm_forced_update_requested = False
                self.wdm.reset_force_update(True)

    def check_available_space(self, file_path):
        """
        Obtains the size of the file on disk and then asks the console whether it will fit or not, unfortunately for
        whf files this is a bit more complicated.

        This works out what kind of database file has been obtained, there are two types that need to be handled differently
        In an ideal world it would be just fired off to the console and whether it gets indigestion or not it should tell me.

        To work out the file type, read the first 2 bytes, if it evaluates to wf then it is a compressed file, if not
        then it's a normal file.
        """
        file_size = os.stat(file_path).st_size
        logging.debug("CT: Size of file is %s bytes" % file_size)

        with open(file_path, "rb") as raw_file:
            #Read first two bytes
            first_bytes = raw_file.read(2)

        if first_bytes == b"wf":
            logging.debug("CT: WHF file")
            decompressed_size = self.get_size_uncompressed_whf(file_path)
            result = self.hano.check_space_available(decompressed_size, file_size)
        else:
            logging.debug("CT: eric.bin file")
            result = self.hano.check_space_available(file_size)

        if result:
            return True
        else:
            return False

    def get_size_uncompressed_whf(self, file_path):
        """
        This is the description of the WHF file
        +--------+------+------------+---------------------------------------------------+
        | Offset | Size |   Value    |                    Description                    |
        +--------+------+------------+---------------------------------------------------+
        | 0      | 2    | b"wf"      | WHF identifier                                    |
        | 2      | 1    | 0x02       | Content type (0x02 is the only relevent value     |
        | 3      | 4    | 0x0000     | Length of description                             |
        | 7      | 4    | 0xXXXX     | Length of data (Probably don't care what this is) |
        | 11     | X    | (Zip file) | This is the zip file itself                       |
        | X      | 4    | 0xXXXX     | 32bit CRC (Probably don't care what this is)      |
        +--------+------+------------+---------------------------------------------------+

        Perhaps this may come  back to bite me, but assuming that all whf files use gzip, then the the last 4 bytes of the
        gzip file contain the file size in bytes, no need to strip bytes, just unzip and measure (thats what she said)
        """
        with open(file_path, "rb") as whf_file:
            whf_file.seek(-8, 2)
            whf_byte = whf_file.read(4)

        uncompressed_size = struct.unpack('I', whf_byte)[0]
        logging.debug("CT: WHF uncompressied size %s" % uncompressed_size)

        return uncompressed_size

    def reset_wdm_state_machine(self):
        """
        Resets the flags
        """
        self.wdm.importer_reset_state_machine()

    def delete_network_flag(self):
        """
        Deletes the network flag so that new network settings can be obtained
        """
        try:
            os.remove(os.path.join(self.config_dir, "network.done"))
            logging.error("CT: Deleting network flag")
        except OSError:
            logging.error("Cannot delete network flag")

    """
    ###################################################################################################################
    FTP Handling
    """
    def update_ftp_payload_details(self, payload_path):
        """
        This is the entry point for another module to tell console task there is a payload to process,
        Assumes that all payloads are zips
        """
        if self.data_update_status == "NTP":
            #Only accept new update details if there is nothing to process?
            self.data_update_filename = payload_path
            self.data_update_available = True
            self.data_update_status = "WTP"

            logging.info("CT: New payload details received")

    def reset_ftp_payload_details(self):
        """
        Puts the status' back to the start
        """
        self.data_update_available = False
        self.data_update_status = "NTP"
        self.data_update_filename = None

    def check_data_payload(self):
        """
        This is where the mainloops checks if there is ann update available to be processed.

        process_payload does a few things (returns 1 if error, otherwise 0):
        1) Unzips the payload
        2) Moves the files to where they need to be
        3) Sets some flags to say what has been updated

        If an eric.bin is received
            The database will need transferring
        If a config is received:
            The application will need restarting
        If a fontlib is received:
            Do nothing
        """
        if self.data_update_available:
            if self.data_update_status == "WTP":
                self.data_update_status = "PROC"
                if self.process_payload(False):     #process_payload returns 1 if there is an error
                    # No point in trying to unzip the file again as its probably invalid, just reset all the flags
                    self.data_update_status = "ERR_UZ"
                    self.data_update_available = False
                    return
                else:
                    #Files successfully extracted, process them accordingly here
                    if self.update_flags["config"]:
                        self.reboot_required = True

                    if self.update_flags["eric"]:
                        self.data_update_status = "XFER"

            if self.data_update_status == "XFER":
                retries = self.config_dict.get("FTP_xfer_retries", 1)
                for x in range(retries):
                    logging.info("CT: Transferring to console")
                    #TODO correct case sensitivity of eric.bin and test this also
                    transfer_status = self.transfer_to_console("/usr/share/payload/eric.bin", "destination_list")

                    if not transfer_status:
                        self.data_update_status = "XFERD"
                    else:
                        self.data_update_status = "ERR_XFER"

                    #Console will reboot whether it completes or not so we need to wait
                    self.console_active = False
                    logging.info("CT: Waiting for console")
                    while not self.console_active:
                        self.check_boot_status()

                    if self.data_update_status == "XFERD":
                        self.data_update_status = "DONE"
                        self.reset_update_flag("eric")
                        break
                    else:
                        self.data_update_status = "FAIL"

            #This currently doesnt do anything there just provisin incase it is needed
            if self.data_update_status == "DONE":
                pass
            elif self.data_update_status == "FAIL":
                pass
            else:
                pass



    """
    ###################################################################################################################
    Updating handling
    """

    def process_payload(self, usb=True):
        if usb:
            payload_name = "xfer.zip"
        else:
            payload_name = self.data_update_filename

        status = self.xfer_handler.unzipPayload(payload_name)

        if status == 0:
            self.xfer_handler.discoverFiles()
        else:
            logging.error("CT: XFER UNZIP ERROR")
            return 1

        self.process_update_files()

        return 0

    def process_update_files(self):
        """
        The idea of this part is to deal with the update files
        :return:
        """
        self.update_flags["eric"] = self.xfer_handler.newEric
        self.update_flags["config"] = self.xfer_handler.newConf
        self.update_flags["fontlib"] = self.xfer_handler.newFont

        self.xfer_handler.newEric = False
        self.xfer_handler.newConf = False
        self.xfer_handler.newFont = False

        logging.info("CT: Eric flag %s" % self.update_flags["eric"])
        logging.info("CT: config flag %s" % self.update_flags["config"])
        logging.info("CT: fontlib flag %s" % self.update_flags["fontlib"])

    def reset_update_flag(self, flagname):
        """
        This resets a given update flag
        """
        self.update_flags[flagname] = False

    def transfer_to_console(self, filepath, filetype):
        """
        Transfers the database back to the console.
        Status is 0 if successful and 1 if not

        For Olli <3
        """
        if self.config_dict.get("DATABASE_hano1_enable_legacy_block", False):
            status = self.hano.send_file(filepath, filetype, "C")
        else:
            status = self.hano.send_file(filepath, filetype)

        return status

    def check_boot_status(self):
        """
        Checks if the console is currently likely in a reboot state
        """
        if len(self.hano.transmitMessage("a", True, 30)) > 0:
            self.console_active = True
        else:
            self.console_active = False

    """
    ###################################################################################################################
    Code polling
    """

    def get_code_lengths(self):
        """Retrieves the length of codes being used by the console"""
        dest_code = self.hano.getDestCode()
        self.des_len = len(dest_code[0])
        logging.info("CT: Console using %s length dest/route code" %self.des_len)

    def update_dest_value(self):
        # Values set on the console, these will return the appropriate code lengths too
        man_dest_code, man_route_code, test_mode = self.hano.getDestCode()
        manual_code = local_code = ("%s%s" % (man_route_code, man_dest_code)).zfill(10)
        logging.info("CT: Dest code: " + local_code + "\t" + "Test Mode: %s" % test_mode)

        self.manual_code = manual_code
        self.test_mode = test_mode

        # SR2378
        self.dest_code = man_dest_code
        self.info_code = self.hano.get_info_code()
        self.route_code = man_route_code

        return manual_code, test_mode

    def get_auto_man(self):
        """
        Determines whether a message was set manually or remotely
        """
        mode = self.hano.get_auto_man_status()

        if mode == "M":
            self.remote_set_code = False
        elif mode == "A":
            self.remote_set_code = True

        logging.info("CT: Remote set code: %s" % self.remote_set_code)

    def get_destination_status(self):
        """
        This obtains the status of the current dest code on the console
        """
        destination_status = self.hano.get_console_status()
        self.destination_status = destination_status

        return destination_status

    def get_valid_dest_code(self):
        """
        It is possible to set invalid codes remotely, so in the case where destination codes are set via 3rd party systems
        there needs to be a way to check if the code is valid if the 3rd party system also supplys destination data as
        text
        """
        valid_code = self.hano.get_code_validity(self.manual_code[2:])
        self.remote_code_valid = valid_code

        return valid_code

    def update_remote_code(self, dest_code, route_code="0000"):
        """
        This method allows the caller to update the remote code that has been obtained via a 3rd party update, it only
        updates the value until the main loop is ready to send the command to the console
        """
        self.remote_route = route_code.zfill(4)
        self.remote_code = dest_code.zfill(4)
        logging.info("CT: New remote destination code: %s%s" % (self.remote_route, self.remote_code))

        self.new_remote_code = True

    def set_dest_code(self):
        """
        This method sets the dest code on the console that has been obtained via a 3rd party system
        """
        if self.new_remote_code:
            logging.info("CT: Setting new destination code: %s" % self.remote_code)
            self.hano.setDestCode(self.remote_code)
            self.hano.setRouteCode(self.remote_route)
            self.hano.setInformationCode(self.remote_info)
            self.data_version = self.hano.obtain_data_version()

            dest_code, route_code, none = self.hano.getDestCode()
            if dest_code == self.remote_code and route_code == self.route_code:
                self.new_remote_code = False
            else:
                logging.error("CT: Remote code was not saved to console")
        else:
            logging.info("CT: No new remote code, skipping")

    # SR2378
    def set_dri_codes(self, dest_code, rt_code, inf_code):
        """
        This sets all the travel focused codes: dest, route, information
        Dest Code:
        Route Code:
        Info Code:
        """
        if dest_code != None:
            self.dest_code = dest_code.zfill(self.des_len)
            self.remote_code = dest_code.zfill(self.des_len)
        if rt_code != None:
            self.remote_route = rt_code.zfill(self.des_len)
        if inf_code != None:
            self.remote_info = inf_code.zfill(self.info_len)
        self.new_remote_code = True

    def process_terminal_mode(self, enable=False):
        """Manages terminal mode"""
        if enable:
            self.terminal_mode = True
            self.hano.start_terminal_mode()
            logging.info("terminal mode enabled")
        else:
            self.terminal_mode = False
            self.hano.end_terminal_mode()
            logging.info("terminal mode disabled")

    def obtain_data_version(self):
        """
        Obtains the current database data version
        """
        self.data_version = self.hano.obtain_data_version()

    """
    ###################################################################################################################
    Sign data

    """

    def obtain_mapped_table(self):
        """
        In order to know what signs to request data for, this routine obtains the sign fitted table, albeit a modified
        version which takes into account the sign mapping.

        Sets self.sign_mapping_table with a list of the sign mapping, where the list index corresponds to the HCP address + 1
        and the value at that index references which sign data it is mapped to.
        """
        valid_map_values = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "A", "B", "C", "D", "E", "-"]

        mapped_table = self.hano.obtain_fitted_signs()

        if len(mapped_table) < 1:
            # self.sign_mapping_table = None
            return

        sign_maps = mapped_table.rstrip("\x00").split(",")

        if len(sign_maps) < 14:     #Should always have 15 signs
            # self.sign_mapping_table = None
            pass    #We just leave it alone i guess
        else:
            temp_table = []

            for map in sign_maps:
                if len(map) == 1:
                    temp_table.append(map)
                else:
                    temp_table.append("-")

            self.sign_mapping_table = temp_table


    def obtain_current_sign_data(self):
        """
        This grabs whatever the current display data is from the console.  It is not expected of this application to obtain
        all the pages or to store everything, but it should at least obtain the current set of sign data.

        Now it may make sense that we try to limit how many times we request information but given the transmission speeds
        and the unlikeliness that there will be several large signs.  If there are duplicate signs mapped it doesn't
        make much sense to rerequest the data, the data should be mapped internally too.

        The pages are incremented after each request, so it is up to the application to deal with page timings
        """
        if self.sign_mapping_table == None:
            return [None]*15

        obtained_data_list = []     #This stores a list of sigdns where the data has already been obtained
        sign_data_list = []

        for index, sign in enumerate(self.sign_mapping_table):
            if index == 15:
                pass
            if sign == "-":
                sign_data_list.append(None)
                continue
            #This is the mapping portion that prevents the onion asking for the same data twice.  Because the console
            #is now sending fully encapsulated HCP messages, so it has been disabled.
            # if sign in obtained_data_list:
            else:
                try:
                    sign_content = self.hano.obtain_sign_content(index+1)
                    if sign_content == "":
                        sign_data_list.append(None)
                    else:
                        sign_data_list.append(sign_content)
                except Exception as e:
                    logging.exception("CT: Cannot obtain data from console")
                    sign_data_list.append(None)


        return sign_data_list

    def obtain_timer_parameters(self):
        """
        As the application is responsible for the page times we need to obtain the parameters set on the console

        T0 - The time of the first page
        T1 - The time of the subsequent pages
        T2 - The time of the info pages (How do we know what page is an info page?)
        T3 - SuperX scrolling time
        """
        try:
            self.first_page_timer = int(self.hano.get_parameter_value("T0"))/10
            self.subsequent_page_timer = int(self.hano.get_parameter_value("T1"))/10
        except ValueError:
            self.first_page_timer = self.subsequent_page_timer = 3

        logging.info("CT: Page timers: T0=%s, T1=%s" % (self.first_page_timer, self.subsequent_page_timer))

        return self.first_page_timer, self.subsequent_page_timer

    def obtain_other_parameters(self):
        """
        This obtains any other relevant parameters that other modules may need
        """
        parameters_to_get = ["SS", "PF"]
        obtained_parameters = {}

        for parameter in parameters_to_get:
            value = self.hano.get_parameter_value(parameter)
            obtained_parameters[parameter] = value

        self.console_parameters = obtained_parameters

    def obtain_sign_firmwares(self):
        """
        This obtains all the sign resolutions for when the console is directly talking to the signs.

        Note: The hano1 call returns values that are stored on the console.  The console does not poll the sign when
        this is requested.  Apparently this is only polled on console startup
        """
        logging.info("CT: Obtaining sign firmware versions")

        sign_firmwares = []

        for sign_address in range(15):
            reply = self.hano.obtain_sign_firmware(sign_address)

            if len(reply) < 10:
                sign_firmwares.append("")
            else:
                #Strip off all the hano1 stuff
                firmware = self.hano.strip_hano_fluff(reply)
                if firmware.startswith("av"):
                    sign_firmwares.append(firmware[3:])
                else:
                    sign_firmwares.append("")

        return sign_firmwares

    def sign_data_loop(self):
        """
        The intention of this, is to offload the task of dealing with page timings and grabbing data to the console_task
        class if the importing application does not want to handle it.

        At the moment because we do not know which is the first page
        """
        parameter_update_interval = 5   #This is the interval in which to recheck all the various parameters
        update_parameter_timer = 0

        while 1:
            if update_parameter_timer == 0 or time.time() - update_parameter_timer > parameter_update_interval:
                self.obtain_mapped_table()
                self.obtain_timer_parameters()

                update_parameter_timer = time.time()

            self.sign_messages = self.obtain_current_sign_data()

            time.sleep(self.subsequent_page_timer)


    """
    ###################################################################################################################
    Main
    """
    def run_sign_data_mode(self):
        """
        For lack of a better name, this will be the new mode where instead of obtaining dest codes to use to lookup in the
        onions copy of the database, this will be concerned with setting the destcodes (if applicable) and then obtaining
        the data from the console.
        """

        parameter_update_interval = 10   #This is the interval in which to recheck all the various parameters
        update_parameter_timer = 0
        page_timer = 0

        try:
            while 1:
                self.co_thread_handler(True)

                self.poll_console()
                self.set_dest_code()
                self.update_dest_value()
                self.get_destination_status()
                self.get_valid_dest_code()
                self.get_auto_man()

                if self.remote_message is not None:
                    self.update_console_display(self.remote_message[0],
                                                self.remote_message[1],
                                                self.remote_message[2])

                self.get_console_digital_inputs()

                # sign test
                if self.remote_test_mode:
                    self.test_mode = self.hano.setTestMode(True)
                    self.remote_test_mode = False
                    self.disable_test_mode = False
                if self.test_mode:
                    #Keep resetting the page time so we dont grab the next set of pages before the sign test was enabled
                    page_timer = time.time()
                    if self.disable_test_mode:
                        self.hano.setTestMode(False)
                        self.disable_test_mode = False

                #Deal with sign data stuff here provided that the page timer has exceeded
                if update_parameter_timer == 0 or time.time() - update_parameter_timer > parameter_update_interval:
                    self.obtain_mapped_table()
                    self.obtain_timer_parameters()
                    self.obtain_other_parameters()
                    update_parameter_timer = time.time()

                if page_timer == 0 or time.time() - page_timer > self.subsequent_page_timer:
                    logging.info("CT: Grabbing sign data")
                    self.sign_messages = self.obtain_current_sign_data()

                    page_timer = time.time()

                if self.wdm_enable:
                    """
                    No need to do any timings for wdm because it is handled by the WDM thread.  Just check whether there
                    is an eric.bin available to do things with.

                    For CTRL_Loading, the code is already obtained above via update_dest_value
                    """
                    self.check_wdm()

                #This is for updates outside of WDM e.g. INIT FTP
                self.check_data_payload()

                if self.monitor_console_flag:
                    self.check_boot_status()

                self.co_thread_handler(False)

                if self.stop or self.reboot_required:
                    self.stop = True
                    break

                time.sleep(self.delay_interval)
        except KeyboardInterrupt:
            self.stop = True

    def run_prod_mode(self):
        """
        This is the default mode if no configuration is loaded, but also, it is possible to get into this mode if:
        1) The console is waiting for a valid network connection and the user presses the LEFT button
        2) F/E is pushed and held should the console go into a boot loop due to an invalid config
        """
        from hanip.onionip import config_updater
        self.cu = config_updater.ConfigUpdater(self.config_dict, self.hw_dict, self.config_dir)
        self.cu.setup_webserver()

        if self.hw_dict.get("unit_IP", None) != None:
            from hanip.itxpt import module_inventory_service
            self.mis = module_inventory_service.ModuleInventoryService(self.config_dict, self.hw_dict)
            self.mis.run()

        from hanip.itxpt import DNS_SD
        self.service_discover = DNS_SD.DNSSD_Discover("_itxpt_http._tcp.local.", "Han_prod")
        self.service_discover.run()

        config_name = self.config_dict.get("CONFIG_name", "N/A")
        mode = self.config_dict.get("MODE_service_mode", "None")
        toggle = False

        while 1:
            if self.service_discover.serviceIP == "":
                if mode == "None" or mode == "factory":
                    self.update_console_display("HanIP %s FACTORY MODE" % self.hw_dict["onion_ver"], 0, 3)
                    time.sleep(1)   #Sleep needed due to how the function above interacts
                    self.update_console_display("Please load configuration", 1, 3)
                else:
                    self.update_console_display("HanIP %s SAFE MODE" % self.hw_dict["onion_ver"], 0, 3)
                    time.sleep(1) #Sleep needed due to how the function above interacts
                    if toggle:
                        self.update_console_display("Mode: %s" % mode, 1, 3)
                    else:
                        self.update_console_display(config_name, 1, 3)
            else:
                self.update_console_display("Production service found!", 1, 3)

            toggle = not toggle
            time.sleep(self.console_poll_interval)
            self.poll_console()

            if self.stop:
                break
            elif self.cu.new_conf:
                # From the web configurator
                break
            elif self.update_flags["config"]:
                # From USB updates
                break

        self.hano.clear_terminal()
        self.hano.end_terminal_mode()

    def run_muted_mode(self):
        """
        Only polls the console for a new config, assume that all network services are unavailable.
        """
        while 1:
            self.poll_console()

            if self.stop or self.reboot_required:
                break

            time.sleep(self.console_poll_interval)

    def run_dev_mode(self):
        """
        As this class is getting more complicated needed a way to get it to do console only stuff as I figure out how
        things fit together.
        """

        while 1:
            self.poll_console()
            self.set_dest_code()
            self.update_dest_value()
            self.get_destination_status()
            self.get_valid_dest_code()
            self.get_auto_man()

            if self.wdm_enable:
                self.check_wdm()

            if self.stop:
                break

            if self.reboot_required:
                break

            time.sleep(self.console_poll_interval)

if __name__ == "__main__":
    pass
