"""
Name: itxpt_mqtt
Title: 
Author: Cooper
Date: 07/08/2019
Last Modified: 14/05/2020
Desc: This is the MQTT implementation of the ITxPT protocol.  Seeing as the signs and console share the initial
startup it made sense to break them out into its own class which branches out to whichever hardware its running on.

Overhauling this module to use the newly created modules for handing of console and sign tasks.
May leave the sign class alone... but at the same time, might split the hires stuff and make it a subclass

Removed hires support as it just made things messy, will have a separate (sub?)class for hires.

"""
import os
import time
import _thread
import logging

import hanip.onionip.hcp
from hanip.debug import print_text

from hanip.itxpt import mqtt_client
from hanip.itxpt import mqtt_payload_parser
from hanip.itxpt import module_inventory_service
from hanip.itxpt import DNS_SD
from hanip.ibis_ip import device_management_service

from hanip.onionip import hcp
from hanip.onionip import hwDetermine
from hanip.onionip import config_updater

itxpt_mqtt_app_ver = ""
logger = logging.getLogger("itxpt_mqtt")

class ITxPTMQTT(object):
    def __init__(self, config_dict, hw_dict, conf_dir, data_dir):
        self.config_dict = config_dict
        self.hw_dict = hw_dict
        self.conf_dir = conf_dir
        self.data_dir = data_dir

    def run(self):
        if self.hw_dict["hw_type"] == "con":
            logging.info("MQTT service running on Console")
            mqtt_service = ConsoleMQTT(self.config_dict, self.hw_dict, self.conf_dir, self.data_dir)
        else:
            logging.info("MQTT service running on Sign")
            mqtt_service = SignMQTT(self.config_dict, self.hw_dict, self.conf_dir, self.data_dir)

        mqtt_service.run()

"""
#######################################################################################################################
CONSOLE STUFF
#######################################################################################################################
"""

class ConsoleMQTT(object):
    def __init__(self, config_dict, hw_dict, conf_dir, data_dir):
        self.config_dict = config_dict
        self.hw_dict = hw_dict
        self.conf_dir = conf_dir
        self.data_dir = data_dir

        self.currentDest = None
        self.waitTime = 0
        self.storedMsg = None

        self.mis = module_inventory_service.ModuleInventoryService(self.config_dict, self.hw_dict)
        self.jsh = mqtt_payload_parser.MQTTPayloadParser(self.config_dict)
        self.hwd = hwDetermine.HardwareDeterminer(None, None, None, self.conf_dir)

    def setup_mis(self):
        self.mis.run()

    def setup_mqtt_client(self):
        self.mqttc = mqtt_client.MQTT_Client()
        self.mqttc.set_broker_address("127.0.0.1")
        #Connect completely insecurely to local host
        self.mqttc.connect_client()

    def setup_mqtt_register(self):
        logging.info("Advertising MQTT broker")
        _thread.start_new_thread(self.broadcast_service, ())

    def setup_template_gen(self):
        from hanip.onionip.sign import template_generator
        self.tg = template_generator.TemplateGenerator(self.config_dict, self.data_dir)

    def setup_console_task(self):
        from hanip.onionip.console import console_task
        self.ct = console_task.ConsoleTask(self.config_dict, self.hw_dict, self.conf_dir, self.data_dir)
        _thread.start_new_thread(self.ct.run_sign_data_mode, ())

    def setup_config_updater(self):
        self.cu = config_updater.ConfigUpdater(self.config_dict, self.hw_dict, self.conf_dir)
        self.cu.setup_webserver()

    def setup_status_handler(self):
        from hanip.itxpt import status_handler
        self.status_handler = status_handler.StatusHandler(self.hw_dict, self.config_dict)

    def broadcast_service(self):
        txtrecord = {
            "txtversion": "1",
            "version": "1",
            "brand": "mosquitto",
            "manufacturer": "Hanover Displays",
            "proto": "3.1",
            "topic": self.config_dict["MQTT_broker_topic"],
        }

        mqtt_broadcast = DNS_SD.ITxPT_DNSSD(self.hw_dict["unit_IP"], "Han_%s_%s" % ("con", self.hw_dict["serial_number"]))
        logging.info("Advertising MQTT Broker via DNS-SD...")
        mqtt_broadcast.mqtt_broker_service(txtrecord)

    def show_terminal_status(self):
        self.ct.show_terminal_status()
        time.sleep(2)

    def displayVersion(self):
        if itxpt_mqtt_app_ver == "":
            module_ver = self.hw_dict["onion_ver"]
        else:
            module_ver = "%s_%s" % (self.hw_dict["onion_ver"], itxpt_mqtt_app_ver)

        self.ct.update_console_display("ITxPT MQTT App Ver: %s" % (module_ver), 0, 3)

    def check_update_state(self):
        #This now needs to check the flags in console task
        for index, state in list(self.ct.update_flags.items()):
            if index == "eric" and state:
                #New eric.bin
                self.ct.reset_update_flag("eric")
            elif index == "config" and state:
                self.ct.reset_update_flag("config")
                return 1
            elif index == "fontlib" and state:
                self.ct.reset_update_flag("fontlib")
                pass
            elif index == "serial" and state:
                pass
            else:
                pass

    def check_config_updater(self):
        if self.cu.new_manu:
            self.cu.new_manu = False

            new_hw_dict = self.hwd.get_serial()
            logging.info("New hw details")

            self.hw_dict["serial_number"] = new_hw_dict["serial_number"]
            self.hw_dict["model"] = new_hw_dict["model"]
            self.hw_dict["hardware_version"] = new_hw_dict["hardware_version"]

            # logging.info(self.hw_dict)
            # Parse now manufacturer details here and pass onto relevant modules, namely MIS
            self.update_mis()
            self.cu.hw_dict = self.hw_dict
            self.ct.hw_dict = self.hw_dict

        if self.cu.new_conf:
            return 1

    def update_mis(self):
        self.mis.unregister_service()
        self.mis.update_information(self.hw_dict)
        self.mis.register_service()

    def get_display_data(self, manual_code, route_number, test_mode, display_data, publish=True):
        """
        This replaces get_destination_data, which ought to be renamed to something else but left as is so nothing breaks
        for the time being.

        As the Onion is now no longer responsible for handling any database parsing, it now obtains the sign display data
        from the console over HANO1.  It will be the job of console_task to obtain sign information as that imports HANO1

        In order for this to work, the application needs to be aware of the sign fitted table, and the mapping table.

        The sign fitted table tells the application where the data is located, the mapping table tells the application
        where to look for the data for a particular sign.

        This now supports PRNS

        """
        destination_mqtt_payloads = []

        if test_mode:
            if manual_code == "0000000000":
                destination_mqtt_payloads.append(self.jsh.convDictToJSON(self.jsh.createConsoleDict("3", "1", None)))
            else:
                destination_mqtt_payloads.append(self.jsh.convDictToJSON(self.jsh.createConsoleDict("3", "0", None)))

            destination_mqtt_payloads = destination_mqtt_payloads*15
        else:
            if display_data == None:
                return

            #We need to convert the display_data into the appropriate JSON payloads for MQTT
            for data in display_data:
                if data == None or len(data) == 0:
                    #TODO: Maybe change this to blank instead of showing the dot, or perhaps split it into None and 0 length
                    destination_mqtt_payloads.append(self.jsh.convDictToJSON(self.jsh.createConsoleDict("0", ".", None)))
                    continue
                elif data[0] == "@":
                    try:
                        index = int(data.lstrip("@"))
                    except ValueError:
                        data = None
                    else:
                        data = display_data[index-1]
                try:
                    destination_mqtt_payloads.append(hcp.RawHMFmsg(data, route_number).encodeAsJSON())
                except hanip.onionip.hcp.HMFError:
                    destination_mqtt_payloads.append(self.jsh.convDictToJSON(self.jsh.createConsoleDict("0", ".", None)))

        if publish:
            self.publish_sign_data(destination_mqtt_payloads)
        else:
            return destination_mqtt_payloads

    def publish_sign_data(self, dest_data):
        for addr, sign_data in enumerate(dest_data, 0):
            publish_topic = self.config_dict["MQTT_broker_topic"].replace("#", str(addr))

            self.mqttc.publish_data(publish_topic, sign_data)

    def publish_console_status(self):
        """
        TODO:  Make this accessible to other protocols that use MQTT as
        :return:
        """
        publish_topic = self.config_dict["MQTT_status_topic"].replace("#", "Console")

        if self.ct.wdm_enable:
            wdm_status = self.ct.wdm.generate_wdm_status()
        else:
            wdm_status = "Not Enabled"

        console_data = {
            "manual_code": self.ct.manual_code,
            "remote_code": self.ct.remote_code,
            "route_code": self.ct.route_code,
            "remote_set_code": self.ct.remote_set_code,
            "test_mode": self.ct.test_mode,
            "wdm_status": wdm_status,
            "sign_map_table": self.ct.sign_mapping_table
        }

        current_status = self.status_handler.get_console_status(time.time(), console_data)
        self.mqttc.publish_data(publish_topic, current_status)

    def run(self):
        self.setup_mis()
        self.setup_mqtt_register()
        self.setup_mqtt_client()
        self.setup_console_task()
        self.setup_config_updater()
        self.setup_status_handler()
        self.displayVersion()

        try:
            while 1:
                self.publish_console_status()

                self.get_display_data(self.ct.manual_code, self.ct.route_code, self.ct.test_mode, self.ct.sign_messages)

                if self.check_update_state():
                    return
                if self.check_config_updater():
                    return
                if self.ct.stop:
                    return

                try:
                    transmit_interval = int(self.config_dict.get("MQTT_transmit_interval", "1"))
                except ValueError:
                    transmit_interval = 1
                time.sleep(transmit_interval)
        except KeyboardInterrupt:
            self.ct.stop = True

"""
#######################################################################################################################
SIGN STUFF MAIN
#######################################################################################################################
"""

class SignMQTT(object):
    def __init__(self, config_dict, hw_dict, conf_dir, data_dir):
        self.config_dict = config_dict
        self.hw_dict = hw_dict
        self.conf_dir = conf_dir
        self.data_dir = data_dir

        self.primary_data = True
        self.acf_wait_time = 0      #Anti-Cooper filter timer

        self.enable_status = True
        self.status_wait_time = 0
        self.status_last_update = 0
        self.enable_echo = True

        self.clear_enable = False   #Override for ecomonitor
        self.blank_signs = False
        self.retransmit = False

        #EcoMode status
        self.ignition_status = "bootup"         # New condition bootup, otherwise on/off
        self.time_since_ignition_loss = 0

        self.mis = module_inventory_service.ModuleInventoryService(self.config_dict, self.hw_dict)
        self.hwd = hwDetermine.HardwareDeterminer(None, None, None, self.conf_dir)
        self.mqpp = mqtt_payload_parser.MQTTPayloadParser(self.config_dict)
        self.hcp = hcp.HCP()

    """
    ###################################################################################################################
    Setup
    """
    def setup_sign_task(self):
        from hanip.onionip.sign import sign_task
        self.st = sign_task.SignTask(self.config_dict, self.hw_dict, self.conf_dir, self.data_dir)

    def setup_mis(self):
        """
        This setups the module inventory service but annoyingly, DMS is required in some instances, so when DMS is on,
        MIS should be off.  Sometimes I think Andrew doesn't like me.

        Remember to set the IBISIP address mapping, otherwise it will use the sign address.
        """
        enable_dms = self.config_dict.get("IBISIP_enable_dms", False)

        if not enable_dms:
            self.mis.run()
        else:
            self.ibisip_dms = device_management_service.DeviceManagementService(self.config_dict, self.hw_dict)
            self.ibisip_dms.setup_dnssd()
            self.ibisip_dms.setup_http_server()

            logging.info("DMS running")

    def setup_ecomode(self):
        from hanip.itxpt import ecoMonitor
        self.ecomon = ecoMonitor.ECOMonitor()

    def setup_consat_status(self):
        from hanip.itxpt import status_handler
        self.status_handler = status_handler.StatusHandler(self.hw_dict, self.config_dict)

        self.vt_config_topic = self.config_dict.get("MQTT_config_topic", None)
        self.vt_system_topic = self.config_dict.get("MQTT_system_topic", None)

    def setup_config_updater(self):
        self.cu = config_updater.ConfigUpdater(self.config_dict, self.hw_dict, self.conf_dir)
        self.cu.setup_webserver()

    def update_mis(self):
        """
        This is for updating the signs own MIS
        :return:
        """
        self.mis.unregister_service()
        self.mis.update_information(self.hw_dict)
        self.mis.register_service()

    """
    ###################################################################################################################
    Configuration
    """
    def check_config_updater(self):
        """
        Before when new manufacturing details were loaded it would re-init the mis and hw_dict but more efficient
        to just restart the app as it is only done in production anyway.
        """
        app_restart_needed = False

        if self.cu.new_manu or self.cu.new_conf:
            app_restart_needed = True

        if self.cu.new_sign_firmware:
            self.cu.new_sign_firmware = False
            self.cu.set_cu_busy(True)

            if self.st.update_firmware(self.cu.signfirmware_path) == "SUCCESS":
                logging.info("Sign software update complete")
                app_restart_needed = True
            else:
                logging.warning("Sign software update failed...")

            self.cu.set_cu_busy(False)

        if self.cu.new_72k_file:
            self.cu.new_72k_file = False
            self.cu.set_cu_busy(True)

            if self.st.update_72k_file(self.cu._72k_filepath) == "SUCCESS":
                logging.info("72k file successfully loaded %s" % self.cu._72k_filepath)
            else:
                logging.warning("72k file load failed...")

            self.cu.set_cu_busy(False)

        return app_restart_needed

    """
    ###################################################################################################################
    Service Connection
    """
    def setup_services(self, override_dict: dict = None):
        """
        This is intended to replace the previous model where there is a 1:1 for a 2:1 but in theory this can support many:1

        Due to the fact that both service discovery and the resulting connection to the MQTT are blocking functions, it
        wasn't possible for the application to handle more than 1 instance.

        This routine should handle the updating sign status' symbols too
        :return:
        """
        if override_dict is None:
            primary_mqtt_conn_dict = self.get_mqtt_conn_dict()
        else:
            primary_mqtt_conn_dict = override_dict

        print("ITxPT: Main conn dict")
        print(primary_mqtt_conn_dict)

        self.primary_data_provider = mqtt_client.MQTTConnectionHandler("Primary", primary_mqtt_conn_dict)

        if self.config_dict.get("MQTT_Secondary_enable", False):
            secondary_mqtt_conn_dict = self.get_mqtt_conn_dict(True)
            print("ITxPT: Second conn dict")
            print(secondary_mqtt_conn_dict)

            self.secondary_data_provider = mqtt_client.MQTTConnectionHandler("Secondary", secondary_mqtt_conn_dict)


    def get_mqtt_conn_dict(self, secondary=False):
        """
        Generates a dictionary of MQTT settings obtained from the config file.  This dictionary will contain a set of
        default values which align with Consat operation.  If this entire section is missing then
        :param secondary:
        :return:
        """
        if secondary:
            dict_prefix = "MQTT_Secondary_"
        else:
            dict_prefix = "MQTT_"

        mqtt_conn_dict = {
            "version": 311,
            "port": 1883,
            "username": "",
            "password": "",
            "discover": True,
            "timeout": 5,
            "disconnect_timeout": 60,
            "service_type": "_mqtt._tcp.local.",
            "hostname": "Han_con",
            "fallback_address": "192.168.3.30",
            "broker_topic": "infohub/dpi/sign/request/#/json",
            "reply_topic": "infohub/dpi/sign/response/#/json",
            "status_topic": "infohub/dpi/sign/status/#/json",
            "enable_tls": False,
            "certificate_path": ""
        }

        for parameter in mqtt_conn_dict:
            #This is the annoying hostname problem
            if parameter == "hostname":
                try:
                    mqtt_conn_dict["hostname"] = self.config_dict[dict_prefix + "hostname"]
                except KeyError:
                    mqtt_conn_dict["hostname"] = self.config_dict["MQTT_primary_hostname"]
            else:
                try:
                    mqtt_conn_dict[parameter] = int(self.config_dict[dict_prefix + "%s" % parameter])
                except KeyError:
                    logging.info("ITXPT_MQTT: Primary MQTT missing %s, using %s" % (parameter, mqtt_conn_dict[parameter]))
                except ValueError:
                    mqtt_conn_dict[parameter] = self.config_dict[dict_prefix + "%s" % parameter]

        if mqtt_conn_dict["status_topic"].lower in ["off", "false", "disabled"]:
            self.enable_status = False
            logging.info("ITXPT_MQTT: Status disabled")
        if mqtt_conn_dict["reply_topic"].lower in ["off", "false", "disabled"]:
            self.enable_echo = False
            logging.info("ITXPT_MQTT: Echo disabled")

        mqtt_conn_dict["address"] = self.hw_dict["address"]
        mqtt_conn_dict["serial"] = self.hw_dict["serial_number"]
        mqtt_conn_dict["mac"] = self.hw_dict["unit_MAC"].replace(":", "")

        #For Adibus that expect a client ID, but we make it standard for everyone
        mqtt_conn_dict["client_id"] = "hanover_" + self.hw_dict["unit_MAC"].replace(":", "")

        return mqtt_conn_dict

    def connect_to_services(self):
        """
        This starts the appropriate threads and also displays the symbols as appropriate on the sign
        :return:
        """
        logging.info("ITxPT: Staring primary data thread")
        _thread.start_new_thread(self.primary_data_provider.run, ())
        time.sleep(1)

        if self.config_dict.get("MQTT_Secondary_enable", False):
            logging.info("ITxPT: Staring secondary data thread")
            _thread.start_new_thread(self.secondary_data_provider.run,())

        #Wait and deal with symbols here.
        while 1:
            self.st.display_sign_graphic("TRI-UP")
            time.sleep(0.5)
            if self.primary_data_provider.service_found:
                break
            if self.config_dict.get("MQTT_Secondary_enable", False):
                if self.primary_data_provider.service_found:
                    break
            self.st.clear_sign()
            time.sleep(0.5)

        while 1:
            self.st.display_sign_graphic("TRI-DOWN")
            time.sleep(0.5)
            if self.primary_data_provider.service_connected:
                break
            if self.config_dict.get("MQTT_Secondary_enable", False):
                if self.secondary_data_provider.service_connected:
                    break
            self.st.clear_sign()
            time.sleep(0.5)

        self.st.display_sign_graphic("SQUARE")
        time.sleep(0.5)

    def disconnect_service(self):
        """
        Just a nice way to terminate the MQTT connection so that if a last will is
        set it doesn't send it.
        """
        self.primary_data_provider.disconnect_client()

        if self.config_dict.get("MQTT_Secondary_enable", False):
            self.secondary_data_provider.disconnect_client()


    def setup_last_will(self, topic, payload, qos: int = 0, retain: bool = False):
        """
        This sets up last will for both brokers if appropriate to set it up.
        """
        logging.info("ITxPT: Setting up Last Will")
        self.primary_data_provider.setup_last_will(topic, payload, qos, retain)

        if self.config_dict.get("MQTT_Secondary_enable", False):
            self.secondary_data_provider.setup_last_will(topic, payload, qos, retain)

    """
    ###################################################################################################################
    ECOMODE
    """

    def handle_ecomode(self):
        """
        Checks the ignition status and handles the ecomode accordingly.
        There needs to be a new function (preferably configurable) where the signs remain in eco2 if they boot up with
        the ignition off, to remain in eco2
        """
        if self.config_dict.get("ECOMODE_enable", False):
            stat, timer = self.ecomon.getIgnitionStatus()

            if stat == "0":
                blank_level = 0
                self.blank_signs = False
                self.ignition_status = "on"
                self.time_since_ignition_loss = 0
            elif self.config_dict.get("ECOMODE_stay_blank", False) and self.ignition_status == "bootup":
                #Sign stays blanked if it never sees ignition on at bootup
                blank_level = 2
                self.blank_signs = True
            else:
                self.ignition_status = "off"
                ign_off_time = (int(time.time() - self.ecomon.ignOffTime))
                self.time_since_ignition_loss = ign_off_time

                if ign_off_time/60 > int(self.config_dict["ECOMODE_blank_after"]):
                    blank_level = 2
                    self.blank_signs = True
                else:
                    blank_level = 1

            self.st.blank_level = blank_level
            self.mis.blank_level = blank_level

    def check_eco2_expiry(self):
        """
        This extends ecomode in systems where the signs never power off, and a requirement for the ignition to be off for
        a given time before a reboot is permitted.
        """
        if self.config_dict.get("ECOMODE_enable", False):
            stat, timer = self.ecomon.getIgnitionStatus()

            eco2_limit = self.config_dict.get("ECOMODE_eco2_limit", 3600)

            try:
                _eco2_limit = int(eco2_limit)
            except ValueError:
                _eco2_limit = 3600

            #Timer in this case is the time in which the ignition went off
            if stat == "1" and (time.time() - timer) > _eco2_limit:
                return True
            else:
                return False
        else:
            return False


    """
    ###################################################################################################################
    Data Handling
    """
    def retrieve_payload_raw(self):
        """
        In some instances we don't want to use the MQTT payload parser as its not compatible/suitable so this just
        checks that there is data and returns it instead.
        """
        if self.primary_data_provider.new_payload:
            self.primary_data_provider.new_payload = False

            return self.primary_data_provider.current_payload
        else:
            return None

    def retrieve_payload(self):
        """
        This portion grabs the payloads from the MQTTConnectionHandler class.  From here it needs to check if there
        is indeed data coming in.

        When the MQTTConnectionHandler class is initiated, the current_payload is set to None.

        The secondary broker, if configured should only ever take over when there is no primary data!!!
        :return:
        """
        #Obtain payloads and only process if they are new payloads but for the primary broker, as long as there
        #is valid data on it, it should be obtained regardless because of remote priority stuff
        new_data = False
        primary_processed_dict = secondary_processed_dict = None

        if self.primary_data_provider.current_payload is not None:
            primary_processed_dict = self.mqpp.process_message(self.primary_data_provider.current_payload)

        if self.primary_data_provider.new_payload:
            new_data = True
            self.primary_data_provider.new_payload = False

        #Obtain secondary payload if appropriate otherwise set it as none
        if self.config_dict.get("MQTT_Secondary_enable", False):
            if self.secondary_data_provider.current_payload is not None:
                secondary_processed_dict = self.mqpp.process_message(self.secondary_data_provider.current_payload)

            if self.secondary_data_provider.new_payload:
                new_data = True
                self.secondary_data_provider.new_payload = False

        #If both brokers do not have valid data, do nothing
        if primary_processed_dict is None and secondary_processed_dict is None:
            return
        #If the primary broker has no data for some reason but the secondary does
        if primary_processed_dict is None:
            processed_dict = secondary_processed_dict
            primary_data = False
        #If the primary broker messages is a clear, and there is a valid secondary message
        elif primary_processed_dict["clear"] and secondary_processed_dict is not None:
            processed_dict = secondary_processed_dict
            primary_data = False
        #Otherwise use the primary dict
        else:
            processed_dict = primary_processed_dict
            primary_data = True

        if new_data or primary_data != self.primary_data:
            logging.info("ITxPT: New Data, primary_source: %s" % primary_data)
            self.process_payload(processed_dict)
            self.primary_data = primary_data
        else:
            logging.info("ITxPT: Identical data, skipping")

    def process_payload(self, payload_dict: dict) -> None:
        """
        This subroutine will deal with the potentially myriad of mqtt payload types, there are currently three
        types but no doubt wil be more, so they will be dealt with here

        Handle remote priority here.
        The problem is, this only grabs the payload, it doesnt know what it contains.  When a console is in IDLE, the
        code sends out a payload with command C.  This is where self.retrieve_command comes in.

        The payload dict looks like this:
        definitions_dict = {
            "type": None,
            "id": "",
            "command": "",
            "message": "",
            "clear": True
            "route_number": None
        }

        So you can cherry pick what you want.

        """
        payload_type = payload_dict["type"]

        #Invalid payload type
        if payload_type == None:
            return
        logging.info("Payload type: %s" % payload_type)

        if payload_type == "rawhmf":
            self.process_rawhmf_message(payload_dict)
        elif payload_type == "consat":
            self.process_message(payload_dict)
        else:
            self.process_generic_message(payload_dict)

    def process_generic_message(self, payload_dict: dict) -> None:
        """
        For all other 3rd party text based protocols where a sign task display dictionary is provided
        """
        display_dict = payload_dict["message"]
        self.st.update_data_dict(display_dict)

        if self.enable_echo:
            pass    #Todo echos for generic messages

    def process_rawhmf_message(self, payload_dict: dict, publish_reply: bool = True) -> None:
        """
        This deals with HMF type messages which are generally sent from Consoles running Hanip
        """
        # logging.info("MQTT: RawHMF message")
        if self.st.test_mode or self.st.ext_test_mode:
            self.st.clear_sign(True)
            self.st.test_mode = self.st.ext_test_mode = False

        self.st.set_route_number(payload_dict.get("route_number", None))
        self.st.update_data(payload_dict["message"])

        reply_msg = print_text.PrintText.to_ascii(payload_dict["message"])
        
        if publish_reply and self.enable_echo:
            self.send_json_reply("rawhmf", reply_msg, None)

    def process_message(self, payload_dict: dict) -> None:
        """

        """
        logging.info("MQTT: Consat message")

        reqid = payload_dict["id"]
        command = payload_dict["command"]
        message = payload_dict["message"]
        route_number = payload_dict.get("route_number", None)

        if route_number is not None:
            self.st.set_route_number(route_number)

        if command == "3":
            if message == "" or message == "0":
                self.st.test_mode = True
            else:
                self.st.ext_test_mode = True
        elif command == "0" or command == "1":
            if self.st.test_mode or self.st.ext_test_mode:
                self.st.test_mode = self.st.ext_test_mode = False
                self.st.clear_sign(True)
            if command == "0":
                self.st.update_data(message)
            else:
                #Need to add the HMF shizz when not rendering
                self.st.update_data(command + "0" + message, False)
        elif command == "2":
            message = self.st.sign_status[0]
        elif command == "9":
            if message == "":
                message = self.st.sign_status[1]
            else:
                reply = self.one_shot_message(command, message)
                message = reply
        elif command == "C":
            self.st.test_mode = self.st.ext_test_mode = False
            self.st.clear_sign(True)
        elif command == "P":
            #A way to change individual configs over mqtt or maybe the entire lot
            pass
        else:
            reply = self.one_shot_message(command, message)
            message = reply

        if self.enable_echo:
            if self.blank_signs and command == "0" or command == "1":
                self.send_json_reply("", "", reqid) #Consat requests that empty fields are replied if signs are blanked
            else:
                self.send_json_reply(command, message, reqid)

    def one_shot_message(self, command, message):
        #This method isnt intended for display messages although will accept them, if display message is sent, it will
        #only appear briefly.  This is more for extended status type messages
        logging.info("Attempting oneshot message...")
        timer = time.time()
        if self.st.sign_task_loop_status:
            self.st.pause = True

        while 1:
            #wait for st loop to stop
            if (time.time() - timer) > 5:
                logging.info("\tTimer exceeded, returning")
                return ""

            if not self.st.sign_task_loop_status:
                logging.info("\tSign task loop paused")
                break

        msg = str(command) + str(self.st.address) + str(message)
        logging.info("\tSending: " + msg)
        reply = self.st.transmit_message(msg, 150)

        self.st.pause = False

        if len(reply) > 0:
            return reply[3:-3]
        else:
            return ""


    def send_json_reply(self, command, msg, reqid):
        """
        Sends a JSON reply to indicate that the sign has recieved the message.

        In the case of two brokers, it sends back a reply containing the message of the broker that has priority.
        :param command:
        :param msg:
        :param reqid:
        :return:
        """
        replyDict = self.mqpp.createReplyDict(command, msg, reqid)
        replyDict["replyData"]["hanover_hcp"]["primary_source"] = self.primary_data
        replyJSON = self.mqpp.convDictToJSON(replyDict)

        self.primary_data_provider.send_reply(replyJSON)
        if self.config_dict.get("MQTT_Secondary_enable", False):
            self.secondary_data_provider.send_reply(replyJSON)

    """
    ###################################################################################################################
    Status Handling
    """

    def send_status(self):
        """
        Sends the current sign status to all connected brokers, this is the original Consat status format, the status'
        has been extended for Vasttrafik but in a separate routine
        :return:
        """

        if self.status_wait_time == 0 or (time.time() - self.status_wait_time) > 30:
            self.status_wait_time = self.status_last_update = time.time()
            if self.enable_status:
                current_status = self.status_handler.get_sign_status(self.status_last_update,
                                                                     self.blank_signs, self.ignition_status, self.time_since_ignition_loss,
                                                                     self.st.sign_status)

                self.primary_data_provider.send_status(current_status)
                if self.config_dict.get("MQTT_Secondary_enable", False):
                    self.secondary_data_provider.send_status(current_status)

            self.send_vt_status()
            self.send_status_other()


    def send_vt_status(self):
        """
        This comes in two parts, the system topic is there to comply with general requirements as stated by the VT-MQTT
        document
        :return:
        """
        if self.vt_system_topic != None:
            vt_system_status = self.status_handler.generate_vt_system_message()

            for key, value in vt_system_status.items():
                system_topic = self.vt_system_topic.replace("#", self.hw_dict["address"]).replace("$SER", self.hw_dict["serial_number"]) + "/" + key

                self.primary_data_provider.send_message(system_topic, value, qos=1, retain=True)

                if self.config_dict.get("MQTT_Secondary_enable", False):
                    self.secondary_data_provider.send_message(system_topic, value, qos=1, retain=True)

        if self.vt_config_topic != None:
            config_topic = self.vt_config_topic.replace("#", self.hw_dict["address"]).replace("$SER", self.hw_dict["serial_number"])

            vt_config_status = self.status_handler.generate_vt_config_message()
            self.primary_data_provider.send_message(config_topic, vt_config_status, qos=1, retain=True)

            if self.config_dict.get("MQTT_Secondary_enable", False):
                self.secondary_data_provider.send_message(config_topic, vt_config_status, qos=1, retain=True)

    def send_status_other(self):
        """
        For whatever other annoying status requirements because its "cool" to be different.

        This will only transmit a status as a single blob to a single topic, if they want more granularity like in the
        send_vt_status then a universal method would need to be written
        """
        status_format = self.config_dict.get("MODE_payload_format", "")
        status_generator = "generate_%s_status_message" % status_format

        if hasattr(self.status_handler, status_generator):
            payload, topic = getattr(self.status_handler, status_generator)()
            self.primary_data_provider.send_message(topic, payload, qos=1, retain=True)

            if self.config_dict.get("MQTT_Secondary_enable", False):
                self.secondary_data_provider.send_message(topic, payload, qos=1, retain=True)

    """
    ###################################################################################################################
    Main
    """

    def run(self):
        self.setup_sign_task()
        self.st.init_serial()
        self.setup_config_updater()
        self.setup_mis()
        self.st.display_sign_graphic("TRI-UP")

        self.setup_services()
        self.connect_to_services()

        self.setup_ecomode()
        self.setup_consat_status()

        _thread.start_new_thread(self.st.run, ())

        try:
            while 1:
                self.handle_ecomode()
                self.retrieve_payload()
                self.send_status()

                if self.check_config_updater():
                    self.st.stop = True
                    return

                if self.st.stop:
                    return

                time.sleep(0.5)
        except KeyboardInterrupt:
            self.st.stop = True
            self.disconnect_service()

    def run_vimi_mode(self):
        """
        This is the loop for VIMI seeing as lot of the starting processes are the same so it has been consolidated
        but there is still some other behavioural differences hence there will still be a VIMI module dealing with
        exclusive things that otherwise does not belong here or fit nicely.
        """
        from hanip.itxpt import vimi_mqtt
        self.vmqtt = vimi_mqtt.VIMIMQTT(self.config_dict, self.hw_dict)

        self.setup_sign_task()
        self.st.init_serial()
        self.setup_config_updater()
        self.setup_mis()
        self.st.display_sign_graphic("TRI-UP")

        #Override the broker topic for VIMI
        mqtt_conn_dict = self.get_mqtt_conn_dict()
        mqtt_conn_dict["broker_topic"] = "/vimi/pis/route/journey"
        self.setup_services(mqtt_conn_dict)

        self.setup_consat_status()
        status_topic, version_topic, config_topic = self.vmqtt.get_vimi_status_topics()
        self.setup_last_will(status_topic, self.vmqtt.get_last_will_payload())
        self.connect_to_services()

        self.setup_ecomode()

        _thread.start_new_thread(self.st.run, ())

        #Publish the VIMI version message once everything is up and running before the main loop
        self.primary_data_provider.send_message(version_topic, self.vmqtt.get_vimi_version_message(), retain=True)
        self.primary_data_provider.send_message(config_topic, self.vmqtt.get_configuration_status(), retain=True)

        try:
            while 1:
                self.handle_ecomode()

                if self.vmqtt.check_uptime_expired() and self.check_eco2_expiry():
                    logging.info("!!---- Rebooting System ----!!")
                    time.sleep(3)
                    os.system("reboot")
                    # os.system("/etc/init.d/hanip restart")


                payload = self.retrieve_payload_raw()

                if payload is not None:
                    sign_data = self.vmqtt.process_message(payload)
                    if sign_data is not None:
                        self.st.update_data_dict(sign_data)

                self.vmqtt.pollStatusChange()
                vimi_status = self.vmqtt.get_status()
                if vimi_status is not None:
                    self.primary_data_provider.send_message(status_topic, vimi_status, retain=True)

                if self.check_config_updater():
                    self.st.stop = True
                    return

                if self.st.stop:
                    return

                time.sleep(0.5)
        except KeyboardInterrupt:
            self.st.stop = True
            self.disconnect_service()

if __name__ == "__main__":
    pass
    # app = ITxPTMQTT()
