Esempio n. 1
0
    def __login_btn_clicked(self, user_name, password):
        """ Slot for the login button

         Tries to create model with the provided user_name and password

        :param str user_name: flow user name
        :param str password: flow user password
        """
        # clear any old status
        self.__display_app.login_view_show_error(None)

        LOGGER.debug("Trying to login with username = {}".format(user_name))

        try:
            # try to initialize model
            self.__model = ClimateControlModel()
            self.__model.connection_status.connect(
                self.__connection_status_result)
            try:
                self.__model.initialize(user_name, password)
            except LoginFailed:
                self.__display_app.login_view_show_error(
                    UIStrings.LOGIN_FAILED)
            except ControllerNotFound:
                self.__display_app.login_view_show_error(
                    UIStrings.CONTROLLER_DEVICE_NOT_FOUND)
            else:
                # Save username if no exception comes
                UserList().add_name(user_name)
                self.__model.setting_received.connect(
                    self.__get_setting_response)
                # define slot for receiving events from Controller
                self.__model.measurement_changed.connect(
                    self.__measurement_changed)
                self.__model.device_status_changed.connect(
                    self.__device_status_changed)
                self.__model.relay_status_changed.connect(
                    self.__relay_status_changed)
                self.__model.controller_status.connect(
                    self.__controller_status)
                self.__model.latency_changed.connect(self.__latency_changed)
                self.__model.get_settings()
        except FlowLibraryError:
            self.__display_app.login_view_show_error(
                UIStrings.FLOW_LIBRARY_ERROR)
    def __login_btn_clicked(self, user_name, password):
        """ Slot for the login button

         Tries to create model with the provided user_name and password
        :param str user_name: flow user name
        :param str password: flow user password
        """
        # clear any old status
        self.__admin_app.login_view_show_error(None)
        LOGGER.debug("Trying to login with username = {}".format(user_name))

        try:
            # try to initialize model
            self.__model = ClimateControlModel()
            self.__model.connection_status.connect(
                self.__connection_status_result)
            self.__model.initialize(user_name, password)
            self.__model.save_setting_result.connect(
                self.__save_setting_result)
            self.__model.command_sending_result.connect(
                self.__command_sending_result)
            self.__model.command_response_received.connect(
                self.__command_response_received)
            self.__model.relay_status_changed.connect(
                self.__relay_status_changed)

            # Save username if no exception comes
            UserList().add_name(user_name)
            # login pass, try to get settings
            self.__model.setting_received.connect(self.__get_setting_response)
            self.__model.get_settings()

        except LoginFailed:
            self.__admin_app.login_view_show_error(Strings.LOGIN_FAILED)
            LOGGER.exception("Login failed")
        except ControllerNotFound:
            self.__admin_app.login_view_show_error(
                Strings.CONTROLLER_DEVICE_NOT_FOUND)
            LOGGER.exception("Controller device not found")
        except FlowLibraryError as error:
            self.__admin_app.login_view_show_error(Strings.FLOW_LIBRARY_ERROR)
            LOGGER.exception(error.message)
    def __login_btn_clicked(self, user_name, password):
        """ Slot for the login button

         Tries to create model with the provided user_name and password

        :param str user_name: flow user name
        :param str password: flow user password
        """
        # clear any old status
        self.__display_app.login_view_show_error(None)

        LOGGER.debug("Trying to login with username = {}".format(user_name))

        try:
            # try to initialize model
            self.__model = ClimateControlModel()
            self.__model.connection_status.connect(self.__connection_status_result)
            try:
                self.__model.initialize(user_name, password)
            except LoginFailed:
                self.__display_app.login_view_show_error(UIStrings.LOGIN_FAILED)
            except ControllerNotFound:
                self.__display_app.login_view_show_error(UIStrings.CONTROLLER_DEVICE_NOT_FOUND)
            else:
                # Save username if no exception comes
                UserList().add_name(user_name)
                self.__model.setting_received.connect(self.__get_setting_response)
                # define slot for receiving events from Controller
                self.__model.measurement_changed.connect(self.__measurement_changed)
                self.__model.device_status_changed.connect(self.__device_status_changed)
                self.__model.relay_status_changed.connect(self.__relay_status_changed)
                self.__model.controller_status.connect(self.__controller_status)
                self.__model.latency_changed.connect(self.__latency_changed)
                self.__model.get_settings()
        except FlowLibraryError:
            self.__display_app.login_view_show_error(UIStrings.FLOW_LIBRARY_ERROR)
    def __login_btn_clicked(self, user_name, password):
        """ Slot for the login button

         Tries to create model with the provided user_name and password
        :param str user_name: flow user name
        :param str password: flow user password
        """
        # clear any old status
        self.__admin_app.login_view_show_error(None)
        LOGGER.debug("Trying to login with username = {}".format(user_name))

        try:
            # try to initialize model
            self.__model = ClimateControlModel()
            self.__model.connection_status.connect(self.__connection_status_result)
            self.__model.initialize(user_name, password)
            self.__model.save_setting_result.connect(self.__save_setting_result)
            self.__model.command_sending_result.connect(self.__command_sending_result)
            self.__model.command_response_received.connect(self.__command_response_received)
            self.__model.relay_status_changed.connect(self.__relay_status_changed)

            # Save username if no exception comes
            UserList().add_name(user_name)
            # login pass, try to get settings
            self.__model.setting_received.connect(self.__get_setting_response)
            self.__model.get_settings()

        except LoginFailed:
            self.__admin_app.login_view_show_error(Strings.LOGIN_FAILED)
            LOGGER.exception("Login failed")
        except ControllerNotFound:
            self.__admin_app.login_view_show_error(Strings.CONTROLLER_DEVICE_NOT_FOUND)
            LOGGER.exception("Controller device not found")
        except FlowLibraryError as error:
            self.__admin_app.login_view_show_error(Strings.FLOW_LIBRARY_ERROR)
            LOGGER.exception(error.message)
class DisplayController(QObject):
    """ Controller class for the display app

    Acts as a coordinator between ClimateControlModel and DisplayView
    """
    def __init__(self, parent=None):
        super(DisplayController, self).__init__(parent)
        _, external_version = get_app_version()
        self.__display_app = DisplayView(external_version)
        self.__display_app.init_login_view(UserList().user_names)
        self.__display_app.register_login_callback(self.__login_btn_clicked)
        self.__display_app.close_signal.connect(self.__close)

        self.__model = None
        self.__refresh_graph_timer = QTimer(self)
        self.__refresh_graph_timer.timeout.connect(self.__speculate_data)
        self.temperature_data = collections.OrderedDict()
        self.humidity_data = collections.OrderedDict()
        self.temperature = None
        self.humidity = None
        self.__display_view_initialized = False

    @Slot(str, str)
    def __login_btn_clicked(self, user_name, password):
        """ Slot for the login button

         Tries to create model with the provided user_name and password

        :param str user_name: flow user name
        :param str password: flow user password
        """
        # clear any old status
        self.__display_app.login_view_show_error(None)

        LOGGER.debug("Trying to login with username = {}".format(user_name))

        try:
            # try to initialize model
            self.__model = ClimateControlModel()
            self.__model.connection_status.connect(self.__connection_status_result)
            try:
                self.__model.initialize(user_name, password)
            except LoginFailed:
                self.__display_app.login_view_show_error(UIStrings.LOGIN_FAILED)
            except ControllerNotFound:
                self.__display_app.login_view_show_error(UIStrings.CONTROLLER_DEVICE_NOT_FOUND)
            else:
                # Save username if no exception comes
                UserList().add_name(user_name)
                self.__model.setting_received.connect(self.__get_setting_response)
                # define slot for receiving events from Controller
                self.__model.measurement_changed.connect(self.__measurement_changed)
                self.__model.device_status_changed.connect(self.__device_status_changed)
                self.__model.relay_status_changed.connect(self.__relay_status_changed)
                self.__model.controller_status.connect(self.__controller_status)
                self.__model.latency_changed.connect(self.__latency_changed)
                self.__model.get_settings()
        except FlowLibraryError:
            self.__display_app.login_view_show_error(UIStrings.FLOW_LIBRARY_ERROR)

    @Slot(dict)
    def __get_setting_response(self, result):
        """ Slot for the get setting response from ClimateControlModel

        Initializes display view with received setting or shows error
        :param dict result: dictionary of the setting to be shown on configuration tab
        """
        if not self.__display_view_initialized:
            self.__display_app.close_login_view()
            self.__display_app.init_display_view(result["setting"])
            self.__display_view_initialized = True
            if result["error"]:
                self.__display_app.update_status_bar(UIStrings.SETTING_NOT_RECEIVED)
                LOGGER.error(result["error"])

        else:
            self.__display_app.update_settings(result["setting"])

    @Slot(dict)
    def __connection_status_result(self, connection_status):
        """Slot function for connection status result

        :param dict connection_status: dictionary containing network and internet status
        """
        status = UIStrings.OK
        color = "green"
        if not connection_status["network"]:
            color = "red"
            status = UIStrings.NETWORK_DOWN
        elif not connection_status["internet"]:
            status = UIStrings.INTERNET_DOWN
            color = "red"
        self.__display_app.set_connection_status(status, color)

    @Slot()
    def __close(self):
        """ Slot function when view gets closed

        Tries to de-initialize the model and stop refresh graph timer if active
        """
        if self.__refresh_graph_timer.isActive():
            self.__refresh_graph_timer.stop()

        LOGGER.debug("closing model")
        if self.__model:
            self.__model.close()

    @Slot(MeasurementEvent)
    def __measurement_changed(self, event):
        """ Slot function which receives measurement event from model

        :param MeasurementEvent event: Measurement Event object received from model
        """
        if self.__refresh_graph_timer.isActive():
            self.__refresh_graph_timer.stop()
        self.temperature = event.temperature
        self.humidity = event.humidity
        self.__update_data(self.temperature, self.humidity)
        self.__display_app.plot_graph(self.temperature_data, self.humidity_data)
        self.__refresh_graph_timer.start(REFRESH_GRAPH_TIMEOUT)

    @Slot()
    def __speculate_data(self):
        """ This function is a slot for refresh_graph timeout event

        It updates measurement data with last received measurement values
        """
        self.__update_data(self.temperature, self.humidity)
        self.__display_app.plot_graph(self.temperature_data, self.humidity_data)
        self.__refresh_graph_timer.start(REFRESH_GRAPH_TIMEOUT)

    def __update_data(self, temperature, humidity):
        """  Updates measurement data
        :param float temperature: temperature value
        :param float humidity: humidity value
        """
        current_time = datetime.datetime.now()
        if not (self.temperature_data and self.humidity_data):
            # fill the dictionary with last GRAPH_WINDOW_SIZE seconds of data
            for i in range(GRAPH_WINDOW_SIZE, 0, -1):
                self.temperature_data[current_time - datetime.timedelta(seconds=i)] = 0
                self.humidity_data[current_time - datetime.timedelta(seconds=i)] = 0

        self.temperature_data[current_time] = temperature
        self.humidity_data[current_time] = humidity
        if current_time - self.temperature_data.keys()[0] > datetime.\
                timedelta(seconds=GRAPH_WINDOW_SIZE):
            del self.temperature_data[self.temperature_data.keys()[0]]
            del self.humidity_data[self.humidity_data.keys()[0]]

    @Slot(str)
    def __controller_status(self, status):
        """ Slot function which receives controller device status from model

        It configures refresh graph timer according to controller device status
        :param str status: controller device status
        """
        LOGGER.debug("Controller status received - {}".format(status))
        if self.__display_app:
            self.__display_app.\
                update_device_status(DeviceEnum.controller, status == "ONLINE")
            # Latency is not applicable if controller is OFFLINE
            if status == "OFFLINE":
                self.__display_app.update_latency(None)
                LOGGER.debug("Latency set to NA")

            if status == "ONLINE" and not self.__refresh_graph_timer.isActive():
                self.__refresh_graph_timer.start(REFRESH_GRAPH_TIMEOUT)
                LOGGER.debug("Refresh graph timer started")

            elif status == "OFFLINE" and self.__refresh_graph_timer.isActive():
                self.__refresh_graph_timer.stop()
                LOGGER.debug("Refresh graph timer stopped")

            elif status == "ONLINE" and self.__refresh_graph_timer.isActive():
                LOGGER.debug("Refresh graph timer is already active")

            elif status == "OFFLINE" and not self.__refresh_graph_timer.isActive():
                LOGGER.debug("Refresh graph timer is already inactive")

        else:
            LOGGER.debug("Controller status received before display window is initialized")

    @Slot(DeviceStatusEvent)
    def __device_status_changed(self, event):
        """ Slot function which receives sensor and actuator status from model

        :param DeviceStatusEvent event: DeviceStatusEvent object
        """
        if self.__display_app:
            self.__display_app.update_device_status(DeviceEnum.sensor, event.sensor_alive)
            self.__display_app.update_device_status(DeviceEnum.actuator, event.actuator_alive)

        else:
            LOGGER.debug("Device(sensor and actuator) status received before display window is "
                         "initialized")

    @Slot(RelayStatusEvent)
    def __relay_status_changed(self, event):
        """ Slot function which receives relay status event from model

        :param RelayStatusEvent event: RelayStatusEvent object
        """
        if self.__display_app:
            self.__display_app.update_relay_status(event)

        else:
            LOGGER.debug("Relay status received before display window is initialized")

    @Slot(float)
    def __latency_changed(self, latency):
        """ Slot function which receives latency from model

        :param float latency: latency value
        """
        if self.__display_app:
            self.__display_app.update_latency(latency)
class AdminController(QObject):
    """ Controller class for the admin app
    Acts as a coordinator between ClimateControlModel and AdminView
    """
    def __init__(self, parent=None):
        """ Creates View and shows the login screen

        """
        super(AdminController, self).__init__(parent)
        _, external_version = get_app_version()
        self.__admin_app = AdminView(external_version)
        self.__admin_app.init_login_view(UserList().user_names)
        self.__admin_app.register_login_callback(self.__login_btn_clicked)
        self.__admin_app.close_signal.connect(self.__close)
        self.__admin_view_initialized = False
        self.__model = None

    @Slot(str, str)
    def __login_btn_clicked(self, user_name, password):
        """ Slot for the login button

         Tries to create model with the provided user_name and password
        :param str user_name: flow user name
        :param str password: flow user password
        """
        # clear any old status
        self.__admin_app.login_view_show_error(None)
        LOGGER.debug("Trying to login with username = {}".format(user_name))

        try:
            # try to initialize model
            self.__model = ClimateControlModel()
            self.__model.connection_status.connect(
                self.__connection_status_result)
            self.__model.initialize(user_name, password)
            self.__model.save_setting_result.connect(
                self.__save_setting_result)
            self.__model.command_sending_result.connect(
                self.__command_sending_result)
            self.__model.command_response_received.connect(
                self.__command_response_received)
            self.__model.relay_status_changed.connect(
                self.__relay_status_changed)

            # Save username if no exception comes
            UserList().add_name(user_name)
            # login pass, try to get settings
            self.__model.setting_received.connect(self.__get_setting_response)
            self.__model.get_settings()

        except LoginFailed:
            self.__admin_app.login_view_show_error(Strings.LOGIN_FAILED)
            LOGGER.exception("Login failed")
        except ControllerNotFound:
            self.__admin_app.login_view_show_error(
                Strings.CONTROLLER_DEVICE_NOT_FOUND)
            LOGGER.exception("Controller device not found")
        except FlowLibraryError as error:
            self.__admin_app.login_view_show_error(Strings.FLOW_LIBRARY_ERROR)
            LOGGER.exception(error.message)

    @Slot(ControllerSetting)
    def __save_setting_btn_clicked(self, setting):
        """ Slot for the save settings button

        Asks model to save the data
        :param ControllerSetting setting: setting object contains various parameter from the view
        """
        self.__admin_app.update_status_bar(None)
        self.__model.save_settings(setting)

    @Slot(ControllerCommandEnum)
    def __manual_control_btn_clicked(self, command):
        """ Slot called when manual control buttons are clicked

        Sends corresponding command to the controller
        :param ControllerCommandEnum command: command to be sent to controller
        """
        # clear previous status message
        self.__admin_app.update_status_bar(None)
        try:
            self.__model.send_command(command)
        except InvalidCommand as error:
            LOGGER.exception(error)

    @Slot(dict)
    def __get_setting_response(self, result):
        """ Slot for the get setting response from ClimateControlModel

        Initializes admin view with received setting or shows error
        :param dict result: dictionary of the setting to be shown on configuration tab
        """
        if not self.__admin_view_initialized:
            self.__admin_app.close_login_view()
            self.__admin_app.init_admin_view(result["setting"])
            self.__admin_app.register_save_setting_callback(
                self.__save_setting_btn_clicked)
            self.__admin_app.register_control_callback(
                self.__manual_control_btn_clicked)
            self.__admin_view_initialized = True
            if result["error"]:
                self.__admin_app.update_status_bar(
                    Strings.SETTING_NOT_RECEIVED)
                LOGGER.error(result["error"])
        else:
            self.__admin_app.update_settings(result["setting"])

    @Slot(dict)
    def __save_setting_result(self, result):
        """ Slot function for save setting result

        Update the UI with the result
        :param dict result: result string is set if any error in saving setting
        """
        if result["error"]:
            self.__admin_app.update_status_bar(Strings.SAVE_SETTING_FAILURE)

    @Slot(dict)
    def __command_sending_result(self, result):
        """ Slot function for command sending result

        :param dict result: "error" set if any command sending failed
        """
        if result["error"]:
            self.__admin_app.update_status_bar(Strings.MESSAGE_SENDING_FAILED)

    @Slot(ControllerResponseEnum)
    def __command_response_received(self, response):
        """ Called when model receives a response message

        :param dict response: response enum
        """
        # translate the responses to UI strings
        ui_strings = {
            ControllerResponseEnum.relay_1_on:
            Strings.HEATER_ON,
            ControllerResponseEnum.relay_1_off:
            Strings.HEATER_OFF,
            ControllerResponseEnum.relay_1_auto:
            Strings.HEATER_AUTO,
            ControllerResponseEnum.relay_2_on:
            Strings.FAN_ON,
            ControllerResponseEnum.relay_2_off:
            Strings.FAN_OFF,
            ControllerResponseEnum.relay_2_auto:
            Strings.FAN_AUTO,
            ControllerResponseEnum.retrieve_settings_success:
            Strings.SAVE_SETTING_SUCCESS,
            ControllerResponseEnum.retrieve_settings_failure.value:
            Strings.SAVE_SETTING_FAILURE
        }
        try:
            self.__admin_app.update_status_bar(
                ui_strings[response["response"]])
        except KeyError:
            LOGGER.exception("Cannot find string for the response {}".format(
                response["response"]))

    @Slot(dict)
    def __connection_status_result(self, connection_status):
        """Slot function for connection status result

        :param dict connection_status: dictionary containing network and internet status
        """
        status = Strings.OK
        color = "green"
        if not connection_status["network"]:
            color = "red"
            status = Strings.NETWORK_DOWN
        elif not connection_status["internet"]:
            status = Strings.INTERNET_DOWN
            color = "red"

        self.__admin_app.set_connection_status(status, color)

    @Slot(RelayStatusEvent)
    def __relay_status_changed(self, event):
        """ Slot called when there is change in relay status or mode

        :param RelayStatusEvent event: relay status event object
        """
        if self.__admin_app:
            # relay 1 is connected to heater and relay 2 is connected to fan
            self.__admin_app.update_manual_buttons_status(
                RelayDevicesEnum.heater, event.relay_1_on, event.relay_1_mode)
            self.__admin_app.update_manual_buttons_status(
                RelayDevicesEnum.fan, event.relay_2_on, event.relay_2_mode)

    @Slot()
    def __close(self):
        """ Slot function when view gets closed

        Tries to de-initialize the model
        """
        LOGGER.debug("closing model")
        if self.__model:
            self.__model.close()
Esempio n. 7
0
class DisplayController(QObject):
    """ Controller class for the display app

    Acts as a coordinator between ClimateControlModel and DisplayView
    """
    def __init__(self, parent=None):
        super(DisplayController, self).__init__(parent)
        _, external_version = get_app_version()
        self.__display_app = DisplayView(external_version)
        self.__display_app.init_login_view(UserList().user_names)
        self.__display_app.register_login_callback(self.__login_btn_clicked)
        self.__display_app.close_signal.connect(self.__close)

        self.__model = None
        self.__refresh_graph_timer = QTimer(self)
        self.__refresh_graph_timer.timeout.connect(self.__speculate_data)
        self.temperature_data = collections.OrderedDict()
        self.humidity_data = collections.OrderedDict()
        self.temperature = None
        self.humidity = None
        self.__display_view_initialized = False

    @Slot(str, str)
    def __login_btn_clicked(self, user_name, password):
        """ Slot for the login button

         Tries to create model with the provided user_name and password

        :param str user_name: flow user name
        :param str password: flow user password
        """
        # clear any old status
        self.__display_app.login_view_show_error(None)

        LOGGER.debug("Trying to login with username = {}".format(user_name))

        try:
            # try to initialize model
            self.__model = ClimateControlModel()
            self.__model.connection_status.connect(
                self.__connection_status_result)
            try:
                self.__model.initialize(user_name, password)
            except LoginFailed:
                self.__display_app.login_view_show_error(
                    UIStrings.LOGIN_FAILED)
            except ControllerNotFound:
                self.__display_app.login_view_show_error(
                    UIStrings.CONTROLLER_DEVICE_NOT_FOUND)
            else:
                # Save username if no exception comes
                UserList().add_name(user_name)
                self.__model.setting_received.connect(
                    self.__get_setting_response)
                # define slot for receiving events from Controller
                self.__model.measurement_changed.connect(
                    self.__measurement_changed)
                self.__model.device_status_changed.connect(
                    self.__device_status_changed)
                self.__model.relay_status_changed.connect(
                    self.__relay_status_changed)
                self.__model.controller_status.connect(
                    self.__controller_status)
                self.__model.latency_changed.connect(self.__latency_changed)
                self.__model.get_settings()
        except FlowLibraryError:
            self.__display_app.login_view_show_error(
                UIStrings.FLOW_LIBRARY_ERROR)

    @Slot(dict)
    def __get_setting_response(self, result):
        """ Slot for the get setting response from ClimateControlModel

        Initializes display view with received setting or shows error
        :param dict result: dictionary of the setting to be shown on configuration tab
        """
        if not self.__display_view_initialized:
            self.__display_app.close_login_view()
            self.__display_app.init_display_view(result["setting"])
            self.__display_view_initialized = True
            if result["error"]:
                self.__display_app.update_status_bar(
                    UIStrings.SETTING_NOT_RECEIVED)
                LOGGER.error(result["error"])

        else:
            self.__display_app.update_settings(result["setting"])

    @Slot(dict)
    def __connection_status_result(self, connection_status):
        """Slot function for connection status result

        :param dict connection_status: dictionary containing network and internet status
        """
        status = UIStrings.OK
        color = "green"
        if not connection_status["network"]:
            color = "red"
            status = UIStrings.NETWORK_DOWN
        elif not connection_status["internet"]:
            status = UIStrings.INTERNET_DOWN
            color = "red"
        self.__display_app.set_connection_status(status, color)

    @Slot()
    def __close(self):
        """ Slot function when view gets closed

        Tries to de-initialize the model and stop refresh graph timer if active
        """
        if self.__refresh_graph_timer.isActive():
            self.__refresh_graph_timer.stop()

        LOGGER.debug("closing model")
        if self.__model:
            self.__model.close()

    @Slot(MeasurementEvent)
    def __measurement_changed(self, event):
        """ Slot function which receives measurement event from model

        :param MeasurementEvent event: Measurement Event object received from model
        """
        if self.__refresh_graph_timer.isActive():
            self.__refresh_graph_timer.stop()
        self.temperature = event.temperature
        self.humidity = event.humidity
        self.__update_data(self.temperature, self.humidity)
        self.__display_app.plot_graph(self.temperature_data,
                                      self.humidity_data)
        self.__refresh_graph_timer.start(REFRESH_GRAPH_TIMEOUT)

    @Slot()
    def __speculate_data(self):
        """ This function is a slot for refresh_graph timeout event

        It updates measurement data with last received measurement values
        """
        self.__update_data(self.temperature, self.humidity)
        self.__display_app.plot_graph(self.temperature_data,
                                      self.humidity_data)
        self.__refresh_graph_timer.start(REFRESH_GRAPH_TIMEOUT)

    def __update_data(self, temperature, humidity):
        """  Updates measurement data
        :param float temperature: temperature value
        :param float humidity: humidity value
        """
        current_time = datetime.datetime.now()
        if not (self.temperature_data and self.humidity_data):
            # fill the dictionary with last GRAPH_WINDOW_SIZE seconds of data
            for i in range(GRAPH_WINDOW_SIZE, 0, -1):
                self.temperature_data[current_time -
                                      datetime.timedelta(seconds=i)] = 0
                self.humidity_data[current_time -
                                   datetime.timedelta(seconds=i)] = 0

        self.temperature_data[current_time] = temperature
        self.humidity_data[current_time] = humidity
        if current_time - self.temperature_data.keys()[0] > datetime.\
                timedelta(seconds=GRAPH_WINDOW_SIZE):
            del self.temperature_data[self.temperature_data.keys()[0]]
            del self.humidity_data[self.humidity_data.keys()[0]]

    @Slot(str)
    def __controller_status(self, status):
        """ Slot function which receives controller device status from model

        It configures refresh graph timer according to controller device status
        :param str status: controller device status
        """
        LOGGER.debug("Controller status received - {}".format(status))
        if self.__display_app:
            self.__display_app.\
                update_device_status(DeviceEnum.controller, status == "ONLINE")
            # Latency is not applicable if controller is OFFLINE
            if status == "OFFLINE":
                self.__display_app.update_latency(None)
                LOGGER.debug("Latency set to NA")

            if status == "ONLINE" and not self.__refresh_graph_timer.isActive(
            ):
                self.__refresh_graph_timer.start(REFRESH_GRAPH_TIMEOUT)
                LOGGER.debug("Refresh graph timer started")

            elif status == "OFFLINE" and self.__refresh_graph_timer.isActive():
                self.__refresh_graph_timer.stop()
                LOGGER.debug("Refresh graph timer stopped")

            elif status == "ONLINE" and self.__refresh_graph_timer.isActive():
                LOGGER.debug("Refresh graph timer is already active")

            elif status == "OFFLINE" and not self.__refresh_graph_timer.isActive(
            ):
                LOGGER.debug("Refresh graph timer is already inactive")

        else:
            LOGGER.debug(
                "Controller status received before display window is initialized"
            )

    @Slot(DeviceStatusEvent)
    def __device_status_changed(self, event):
        """ Slot function which receives sensor and actuator status from model

        :param DeviceStatusEvent event: DeviceStatusEvent object
        """
        if self.__display_app:
            self.__display_app.update_device_status(DeviceEnum.sensor,
                                                    event.sensor_alive)
            self.__display_app.update_device_status(DeviceEnum.actuator,
                                                    event.actuator_alive)

        else:
            LOGGER.debug(
                "Device(sensor and actuator) status received before display window is "
                "initialized")

    @Slot(RelayStatusEvent)
    def __relay_status_changed(self, event):
        """ Slot function which receives relay status event from model

        :param RelayStatusEvent event: RelayStatusEvent object
        """
        if self.__display_app:
            self.__display_app.update_relay_status(event)

        else:
            LOGGER.debug(
                "Relay status received before display window is initialized")

    @Slot(float)
    def __latency_changed(self, latency):
        """ Slot function which receives latency from model

        :param float latency: latency value
        """
        if self.__display_app:
            self.__display_app.update_latency(latency)
class AdminController(QObject):
    """ Controller class for the admin app
    Acts as a coordinator between ClimateControlModel and AdminView
    """

    def __init__(self, parent=None):
        """ Creates View and shows the login screen

        """
        super(AdminController, self).__init__(parent)
        _, external_version = get_app_version()
        self.__admin_app = AdminView(external_version)
        self.__admin_app.init_login_view(UserList().user_names)
        self.__admin_app.register_login_callback(self.__login_btn_clicked)
        self.__admin_app.close_signal.connect(self.__close)
        self.__admin_view_initialized = False
        self.__model = None

    @Slot(str, str)
    def __login_btn_clicked(self, user_name, password):
        """ Slot for the login button

         Tries to create model with the provided user_name and password
        :param str user_name: flow user name
        :param str password: flow user password
        """
        # clear any old status
        self.__admin_app.login_view_show_error(None)
        LOGGER.debug("Trying to login with username = {}".format(user_name))

        try:
            # try to initialize model
            self.__model = ClimateControlModel()
            self.__model.connection_status.connect(self.__connection_status_result)
            self.__model.initialize(user_name, password)
            self.__model.save_setting_result.connect(self.__save_setting_result)
            self.__model.command_sending_result.connect(self.__command_sending_result)
            self.__model.command_response_received.connect(self.__command_response_received)
            self.__model.relay_status_changed.connect(self.__relay_status_changed)

            # Save username if no exception comes
            UserList().add_name(user_name)
            # login pass, try to get settings
            self.__model.setting_received.connect(self.__get_setting_response)
            self.__model.get_settings()

        except LoginFailed:
            self.__admin_app.login_view_show_error(Strings.LOGIN_FAILED)
            LOGGER.exception("Login failed")
        except ControllerNotFound:
            self.__admin_app.login_view_show_error(Strings.CONTROLLER_DEVICE_NOT_FOUND)
            LOGGER.exception("Controller device not found")
        except FlowLibraryError as error:
            self.__admin_app.login_view_show_error(Strings.FLOW_LIBRARY_ERROR)
            LOGGER.exception(error.message)

    @Slot(ControllerSetting)
    def __save_setting_btn_clicked(self, setting):
        """ Slot for the save settings button

        Asks model to save the data
        :param ControllerSetting setting: setting object contains various parameter from the view
        """
        self.__admin_app.update_status_bar(None)
        self.__model.save_settings(setting)

    @Slot(ControllerCommandEnum)
    def __manual_control_btn_clicked(self, command):
        """ Slot called when manual control buttons are clicked

        Sends corresponding command to the controller
        :param ControllerCommandEnum command: command to be sent to controller
        """
        # clear previous status message
        self.__admin_app.update_status_bar(None)
        try:
            self.__model.send_command(command)
        except InvalidCommand as error:
            LOGGER.exception(error)

    @Slot(dict)
    def __get_setting_response(self, result):
        """ Slot for the get setting response from ClimateControlModel

        Initializes admin view with received setting or shows error
        :param dict result: dictionary of the setting to be shown on configuration tab
        """
        if not self.__admin_view_initialized:
            self.__admin_app.close_login_view()
            self.__admin_app.init_admin_view(result["setting"])
            self.__admin_app.register_save_setting_callback(self.__save_setting_btn_clicked)
            self.__admin_app.register_control_callback(self.__manual_control_btn_clicked)
            self.__admin_view_initialized = True
            if result["error"]:
                self.__admin_app.update_status_bar(Strings.SETTING_NOT_RECEIVED)
                LOGGER.error(result["error"])
        else:
            self.__admin_app.update_settings(result["setting"])

    @Slot(dict)
    def __save_setting_result(self, result):
        """ Slot function for save setting result

        Update the UI with the result
        :param dict result: result string is set if any error in saving setting
        """
        if result["error"]:
            self.__admin_app.update_status_bar(Strings.SAVE_SETTING_FAILURE)

    @Slot(dict)
    def __command_sending_result(self, result):
        """ Slot function for command sending result

        :param dict result: "error" set if any command sending failed
        """
        if result["error"]:
            self.__admin_app.update_status_bar(Strings.MESSAGE_SENDING_FAILED)

    @Slot(ControllerResponseEnum)
    def __command_response_received(self, response):
        """ Called when model receives a response message

        :param dict response: response enum
        """
        # translate the responses to UI strings
        ui_strings = {
            ControllerResponseEnum.relay_1_on: Strings.HEATER_ON,
            ControllerResponseEnum.relay_1_off: Strings.HEATER_OFF,
            ControllerResponseEnum.relay_1_auto: Strings.HEATER_AUTO,
            ControllerResponseEnum.relay_2_on: Strings.FAN_ON,
            ControllerResponseEnum.relay_2_off: Strings.FAN_OFF,
            ControllerResponseEnum.relay_2_auto: Strings.FAN_AUTO,
            ControllerResponseEnum.retrieve_settings_success: Strings.SAVE_SETTING_SUCCESS,
            ControllerResponseEnum.retrieve_settings_failure.value: Strings.SAVE_SETTING_FAILURE,
        }
        try:
            self.__admin_app.update_status_bar(ui_strings[response["response"]])
        except KeyError:
            LOGGER.exception("Cannot find string for the response {}".format(response["response"]))

    @Slot(dict)
    def __connection_status_result(self, connection_status):
        """Slot function for connection status result

        :param dict connection_status: dictionary containing network and internet status
        """
        status = Strings.OK
        color = "green"
        if not connection_status["network"]:
            color = "red"
            status = Strings.NETWORK_DOWN
        elif not connection_status["internet"]:
            status = Strings.INTERNET_DOWN
            color = "red"

        self.__admin_app.set_connection_status(status, color)

    @Slot(RelayStatusEvent)
    def __relay_status_changed(self, event):
        """ Slot called when there is change in relay status or mode

        :param RelayStatusEvent event: relay status event object
        """
        if self.__admin_app:
            # relay 1 is connected to heater and relay 2 is connected to fan
            self.__admin_app.update_manual_buttons_status(RelayDevicesEnum.heater, event.relay_1_on, event.relay_1_mode)
            self.__admin_app.update_manual_buttons_status(RelayDevicesEnum.fan, event.relay_2_on, event.relay_2_mode)

    @Slot()
    def __close(self):
        """ Slot function when view gets closed

        Tries to de-initialize the model
        """
        LOGGER.debug("closing model")
        if self.__model:
            self.__model.close()