Esempio n. 1
0
class DeviceDialog(QDialog, Ui_DeviceDialog):
    """
        Function and Event handling class for the Ui_DeviceDialog.
    """
    qtcb_enumerate = pyqtSignal(str, str, 'char', type((0, )), type((0, )),
                                int, int)
    qtcb_connected = pyqtSignal(int)

    def __init__(self, parent):
        QDialog.__init__(self, parent, get_modeless_dialog_flags())

        self._logger_window = parent

        self.qtcb_enumerate.connect(self.cb_enumerate)
        self.qtcb_connected.connect(self.cb_connected)

        self.host = None
        self.port = None
        self.secret = None

        self.ipcon = IPConnection()
        self.ipcon.register_callback(IPConnection.CALLBACK_CONNECTED,
                                     self.qtcb_connected.emit)
        self.ipcon.register_callback(IPConnection.CALLBACK_ENUMERATE,
                                     self.qtcb_enumerate.emit)

        self.setupUi(self)

        self.btn_add_device.clicked.connect(self.btn_add_device_clicked)
        self.btn_refresh.clicked.connect(self.btn_refresh_clicked)
        self.btn_close.clicked.connect(self.btn_close_clicked)
        self.tree_widget.itemActivated.connect(self.add_item)

        self.connected_uids = []
        self.available_item = QTreeWidgetItem(['No devices available'])
        self.supported_item = QTreeWidgetItem(['Supported devices'])

        self.tree_widget.addTopLevelItem(self.available_item)
        self.tree_widget.addTopLevelItem(self.supported_item)

        for device_name in device_specs:
            self.supported_item.addChild(QTreeWidgetItem([device_name]))

        self.supported_item.sortChildren(0, Qt.AscendingOrder)
        self.supported_item.setExpanded(True)

    def cb_connected(self, connect_reason):
        self.tree_widget.clearSelection()
        self.available_item.takeChildren()
        self.available_item.setExpanded(True)
        self.available_item.setText(
            0, 'No devices available at {0}:{1}'.format(self.host, self.port))

        self.connected_uids = []

        if self.secret != None:
            self.ipcon.set_auto_reconnect(
                False)  # don't auto-reconnect on authentication error

            try:
                self.ipcon.authenticate(self.secret)
            except:
                try:
                    self.ipcon.disconnect()
                except:
                    pass

                if connect_reason == IPConnection.CONNECT_REASON_AUTO_RECONNECT:
                    extra = ' after auto-reconnect'
                else:
                    extra = ''

                self.available_item.setText(0,
                                            'Could not authenticate' + extra)
                return

            self.ipcon.set_auto_reconnect(True)

        try:
            self.ipcon.enumerate()
        except:
            pass

    def cb_enumerate(self, uid, connected_uid, position, hardware_version,
                     firmware_version, device_identifier, enumeration_type):
        if enumeration_type in [IPConnection.ENUMERATION_TYPE_AVAILABLE,
                                IPConnection.ENUMERATION_TYPE_CONNECTED] and \
           uid not in self.connected_uids:
            try:
                display_name = device_factory.get_device_display_name(
                    device_identifier)
            except KeyError:
                return  # unknown device identifier

            if display_name in device_specs:
                self.connected_uids.append(uid)
                self.available_item.addChild(
                    QTreeWidgetItem(['{0} [{1}]'.format(display_name, uid)]))
                self.available_item.setText(
                    0, 'Devices available at {0}:{1}'.format(
                        self.host, self.port))
                self.available_item.sortChildren(0, Qt.AscendingOrder)
        else:
            if uid in self.connected_uids:
                self.connected_uids.remove(uid)

            for i in range(self.available_item.childCount()):
                child = self.available_item.child(i)

                if '[{0}]'.format(uid) in child.text(0):
                    self.available_item.takeChild(i)
                    break

            if self.available_item.childCount() == 0:
                self.available_item.setText(
                    0, 'No devices available at {0}:{1}'.format(
                        self.host, self.port))

    def btn_add_device_clicked(self):
        for item in self.tree_widget.selectedItems():
            if item == self.available_item or item == self.supported_item:
                continue

            self._logger_window.add_device_to_tree(
                self.create_device_config(item.text(0)))

    def btn_refresh_clicked(self):
        try:
            self.ipcon.disconnect()
        except:
            pass

        self.tree_widget.clearSelection()
        self.available_item.takeChildren()
        self.available_item.setExpanded(True)

        self.connected_uids = []
        self.host = self._logger_window.combo_host.currentText()
        self.port = self._logger_window.spin_port.value()

        if self._logger_window.check_authentication.isChecked():
            try:
                self.secret = self._logger_window.edit_secret.text().encode(
                    'ascii')
            except:
                self.secret = None
        else:
            self.secret = None

        try:
            self.ipcon.connect(self.host, self.port)
            self.available_item.setText(
                0,
                'No devices available at {0}:{1}'.format(self.host, self.port))
        except:
            self.available_item.setText(
                0, 'Could not connect to {0}:{1}'.format(self.host, self.port))

    def btn_close_clicked(self):
        self.close()

    def add_item(self, item):
        if item == self.available_item or item == self.supported_item:
            return

        self._logger_window.add_device_to_tree(
            self.create_device_config(item.text(0)))

    def create_device_config(self, item_text):
        name, uid = Utilities.parse_device_name(item_text)  # FIXME
        device_spec = device_specs[name]

        if uid == None:  # FIXME
            uid = ''

        device = {
            'host': 'default',
            'name': name,
            'uid': uid,
            'values': {},
            'options': {}
        }

        for value_spec in device_spec['values']:
            device['values'][value_spec['name']] = {'interval': 0}

            if value_spec['subvalues'] != None:
                device['values'][value_spec['name']]['subvalues'] = {}

                for subvalue_name in value_spec['subvalues']:
                    device['values'][
                        value_spec['name']]['subvalues'][subvalue_name] = True

        if device_spec['options'] != None:
            for option_spec in device_spec['options']:
                device['options'][option_spec['name']] = {
                    'value': option_spec['default']
                }

        return device
Esempio n. 2
0
class DataLogger(threading.Thread):
    """
    This class represents the data logger and an object of this class is
    the actual instance of a logging process
    """

    # constructor and other functions
    def __init__(self, config, gui_job):
        super().__init__()

        self.daemon = True

        self.jobs = []  # thread hashmap for all running threads/jobs
        self.job_exit_flag = False  # flag for stopping the thread
        self.job_sleep = 1  # TODO: Enahncement -> use condition objects
        self.timers = []
        self._gui_job = gui_job
        self.data_queue = {}  # universal data_queue hash map
        self.host = config['hosts']['default']['name']
        self.port = config['hosts']['default']['port']
        self.secret = config['hosts']['default']['secret']

        if self.secret != None:
            try:
                self.secret.encode('ascii')
            except:
                EventLogger.critical('Authentication secret cannot contain non-ASCII characters')
                self.secret = None

        self.loggable_devices = []
        self.ipcon = IPConnection()

        self.ipcon.register_callback(IPConnection.CALLBACK_CONNECTED, self.cb_connected)
        self.ipcon.register_callback(IPConnection.CALLBACK_ENUMERATE, self.cb_enumerate)

        try:
            self.ipcon.connect(self.host, self.port)  # Connect to brickd
        except Exception as e:
            EventLogger.critical("A critical error occur: " + str(e))
            self.ipcon = None
            raise DataLoggerException(DataLoggerException.DL_CRITICAL_ERROR, "A critical error occur: " + str(e))

        EventLogger.info("Connection to " + self.host + ":" + str(self.port) + " established.")
        self.ipcon.set_timeout(1)  # TODO: Timeout number
        EventLogger.debug("Set ipcon.time_out to 1.")
        self._config = config
        self.csv_file_name = 'logger_data_{0}.csv'.format(int(time.time()))
        self.csv_enabled = True
        self.stopped = False

    def cb_connected(self, connect_reason):
        if self.secret != None:
            try:
                self.secret.encode('ascii')
            except:
                try:
                    self.ipcon.disconnect()
                except:
                    pass

                EventLogger.critical('Authentication secret cannot contain non-ASCII characters')
                return

            self.ipcon.set_auto_reconnect(False) # don't auto-reconnect on authentication error

            try:
                self.ipcon.authenticate(self.secret)
            except:
                try:
                    self.ipcon.disconnect()
                except:
                    pass

                if connect_reason == IPConnection.CONNECT_REASON_AUTO_RECONNECT:
                    extra = ' after auto-reconnect'
                else:
                    extra = ''

                EventLogger.critical('Could not authenticate' + extra)
                return

            self.ipcon.set_auto_reconnect(True)

            EventLogger.info("Successfully authenticated")

        self.apply_options()

    def cb_enumerate(self, uid, connected_uid, position,
                     hardware_version, firmware_version,
                     device_identifier, enumeration_type):
        if enumeration_type in [IPConnection.ENUMERATION_TYPE_AVAILABLE,
                                IPConnection.ENUMERATION_TYPE_CONNECTED]:
            self.apply_options()

    def apply_options(self):
        for loggable_device in self.loggable_devices:
            loggable_device.apply_options()

    def process_data_csv_section(self):
        """
        Information out of the general section will be consumed here
        """
        csv = self._config['data']['csv']

        self.csv_enabled = csv['enabled']
        self.csv_file_name = csv['file_name']

        if self.csv_enabled:
            EventLogger.info("Logging data to CSV file: " + str(self.csv_file_name))

    def initialize_loggable_devices(self):
        """
        This function creates the actual objects for each device out of the configuration
        """
        # start the timers
        for device in self._config['devices']:
            if len(device['uid']) == 0:
                EventLogger.warning('Ignoring "{0}" with empty UID'.format(device['name']))
                continue

            try:
                decoded_uid = base58decode(device['uid'])
            except:
                EventLogger.warning('Ignoring "{0}" with invalid UID: {1}'.format(device['name'], device['uid']))
                continue

            if decoded_uid < 1 or decoded_uid > 0xFFFFFFFF:
                EventLogger.warning('Ignoring "{0}" with out-of-range UID: {1}'.format(device['name'], device['uid']))
                continue

            try:
                loggable_device = DeviceImpl(device, self)
                loggable_device.start_timer()
            except Exception as e:
                msg = "A critical error occur: " + str(e)
                self.stop()
                raise DataLoggerException(DataLoggerException.DL_CRITICAL_ERROR, msg)

            self.loggable_devices.append(loggable_device)

        self.apply_options()

    def run(self):
        """
        This function starts the actual logging process in a new thread
        """
        self.stopped = False
        self.process_data_csv_section()

        self.initialize_loggable_devices()

        """START-WRITE-THREAD"""
        # create jobs
        # look which thread should be working
        if self.csv_enabled:
            self.jobs.append(CSVWriterJob(name="CSV-Writer", datalogger=self))
        if self._gui_job is not None:
            self._gui_job.set_datalogger(self)
            self.jobs.append(self._gui_job)

        for t in self.jobs:
            t.start()
        EventLogger.debug("Jobs started.")

        """START-TIMERS"""
        for t in self.timers:
            t.start()
        EventLogger.debug("Get-Timers started.")

        """END_CONDITIONS"""
        EventLogger.info("DataLogger is running...")
        # TODO Exit condition ?

    def stop(self):
        """
        This function ends the logging process. self.stopped will be set to True if
        the data logger stops
        """
        EventLogger.info("Closing Timers and Threads...")

        """CLEANUP_AFTER_STOP """
        # check if all timers stopped
        for t in self.timers:
            t.stop()
        for t in self.timers:
            t.join()
        EventLogger.debug("Get-Timers[" + str(len(self.timers)) + "] stopped.")

        # set THREAD_EXIT_FLAG for all work threads
        for job in self.jobs:
            job.stop()
        # wait for all threads to stop
        for job in self.jobs:
            job.join()
        EventLogger.debug("Jobs[" + str(len(self.jobs)) + "] stopped.")

        try:
            self.ipcon.disconnect()
        except:
            pass

        EventLogger.info("Connection closed successfully.")

        self.stopped = True

    def add_to_queue(self, csv):
        """
        Adds logged data to all queues which are registered in 'self.data_queue'

        csv --
        """
        for q in self.data_queue.values():
            q.put(csv)
Esempio n. 3
0
class DataLogger(threading.Thread):
    """
    This class represents the data logger and an object of this class is
    the actual instance of a logging process
    """

    # constructor and other functions
    def __init__(self, config, gui_job):
        super(DataLogger, self).__init__()

        self.daemon = True

        self.jobs = []  # thread hashmap for all running threads/jobs
        self.job_exit_flag = False  # flag for stopping the thread
        self.job_sleep = 1  # TODO: Enahncement -> use condition objects
        self.timers = []
        self._gui_job = gui_job
        self.data_queue = {}  # universal data_queue hash map
        self.host = config['hosts']['default']['name']
        self.port = config['hosts']['default']['port']
        self.secret = config['hosts']['default']['secret']

        if self.secret != None:
            try:
                self.secret = self.secret.encode('ascii')
            except:
                EventLogger.critical(
                    'Authentication secret cannot contain non-ASCII characters'
                )
                self.secret = None

        self.loggable_devices = []
        self.ipcon = IPConnection()

        self.ipcon.register_callback(IPConnection.CALLBACK_CONNECTED,
                                     self.cb_connected)
        self.ipcon.register_callback(IPConnection.CALLBACK_ENUMERATE,
                                     self.cb_enumerate)

        try:
            self.ipcon.connect(self.host, self.port)  # Connect to brickd
        except Exception as e:
            EventLogger.critical("A critical error occur: " + str(e))
            self.ipcon = None
            raise DataLoggerException(DataLoggerException.DL_CRITICAL_ERROR,
                                      "A critical error occur: " + str(e))

        EventLogger.info("Connection to " + self.host + ":" + str(self.port) +
                         " established.")
        self.ipcon.set_timeout(1)  # TODO: Timeout number
        EventLogger.debug("Set ipcon.time_out to 1.")
        self._config = config
        self.csv_file_name = 'logger_data_{0}.csv'.format(int(time.time()))
        self.csv_enabled = True
        self.stopped = False

    def cb_connected(self, connect_reason):
        if self.secret != None:
            try:
                secret = self.secret.encode('ascii')
            except:
                try:
                    self.ipcon.disconnect()
                except:
                    pass

                EventLogger.critical(
                    'Authentication secret cannot contain non-ASCII characters'
                )
                return

            self.ipcon.set_auto_reconnect(
                False)  # don't auto-reconnect on authentication error

            try:
                self.ipcon.authenticate(secret)
            except:
                try:
                    self.ipcon.disconnect()
                except:
                    pass

                if connect_reason == IPConnection.CONNECT_REASON_AUTO_RECONNECT:
                    extra = ' after auto-reconnect'
                else:
                    extra = ''

                EventLogger.critical('Could not authenticate' + extra)
                return

            self.ipcon.set_auto_reconnect(True)

            EventLogger.info("Successfully authenticated")

        self.apply_options()

    def cb_enumerate(self, uid, connected_uid, position, hardware_version,
                     firmware_version, device_identifier, enumeration_type):
        if enumeration_type in [
                IPConnection.ENUMERATION_TYPE_AVAILABLE,
                IPConnection.ENUMERATION_TYPE_CONNECTED
        ]:
            self.apply_options()

    def apply_options(self):
        for loggable_device in self.loggable_devices:
            loggable_device.apply_options()

    def process_data_csv_section(self):
        """
        Information out of the general section will be consumed here
        """
        csv = self._config['data']['csv']

        self.csv_enabled = csv['enabled']
        self.csv_file_name = csv['file_name']

        if self.csv_enabled:
            EventLogger.info("Logging data to CSV file: " +
                             str(self.csv_file_name))

    def initialize_loggable_devices(self):
        """
        This function creates the actual objects for each device out of the configuration
        """
        # start the timers
        for device in self._config['devices']:
            if len(device['uid']) == 0:
                EventLogger.warning('Ignoring "{0}" with empty UID'.format(
                    device['name']))
                continue

            try:
                decoded_uid = base58decode(device['uid'])
            except:
                EventLogger.warning(
                    'Ignoring "{0}" with invalid UID: {1}'.format(
                        device['name'], device['uid']))
                continue

            if decoded_uid < 1 or decoded_uid > 0xFFFFFFFF:
                EventLogger.warning(
                    'Ignoring "{0}" with out-of-range UID: {1}'.format(
                        device['name'], device['uid']))
                continue

            try:
                loggable_device = DeviceImpl(device, self)
                loggable_device.start_timer()
            except Exception as e:
                msg = "A critical error occur: " + str(e)
                self.stop()
                raise DataLoggerException(
                    DataLoggerException.DL_CRITICAL_ERROR, msg)

            self.loggable_devices.append(loggable_device)

        self.apply_options()

    def run(self):
        """
        This function starts the actual logging process in a new thread
        """
        self.stopped = False
        self.process_data_csv_section()

        self.initialize_loggable_devices()
        """START-WRITE-THREAD"""
        # create jobs
        # look which thread should be working
        if self.csv_enabled:
            self.jobs.append(CSVWriterJob(name="CSV-Writer", datalogger=self))
        if self._gui_job is not None:
            self._gui_job.set_datalogger(self)
            self.jobs.append(self._gui_job)

        for t in self.jobs:
            t.start()
        EventLogger.debug("Jobs started.")
        """START-TIMERS"""
        for t in self.timers:
            t.start()
        EventLogger.debug("Get-Timers started.")
        """END_CONDITIONS"""
        EventLogger.info("DataLogger is running...")
        # TODO Exit condition ?

    def stop(self):
        """
        This function ends the logging process. self.stopped will be set to True if
        the data logger stops
        """
        EventLogger.info("Closing Timers and Threads...")
        """CLEANUP_AFTER_STOP """
        # check if all timers stopped
        for t in self.timers:
            t.stop()
        for t in self.timers:
            t.join()
        EventLogger.debug("Get-Timers[" + str(len(self.timers)) + "] stopped.")

        # set THREAD_EXIT_FLAG for all work threads
        for job in self.jobs:
            job.stop()
        # wait for all threads to stop
        for job in self.jobs:
            job.join()
        EventLogger.debug("Jobs[" + str(len(self.jobs)) + "] stopped.")

        try:
            self.ipcon.disconnect()
        except:
            pass

        EventLogger.info("Connection closed successfully.")

        self.stopped = True

    def add_to_queue(self, csv):
        """
        Adds logged data to all queues which are registered in 'self.data_queue'

        csv --
        """
        for q in self.data_queue.values():
            q.put(csv)
Esempio n. 4
0
class MainWindow(QMainWindow, Ui_MainWindow):
    qtcb_enumerate = pyqtSignal(str, str, 'char', type((0,)), type((0,)), int, int)
    qtcb_connected = pyqtSignal(int)
    qtcb_disconnected = pyqtSignal(int)

    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        self.setupUi(self)
        self.setWindowIcon(QIcon(os.path.join(get_program_path(), "brickv-icon.png")))
        signal.signal(signal.SIGINT, self.exit_brickv)
        signal.signal(signal.SIGTERM, self.exit_brickv)

        self.async_thread = async_start_thread(self)

        self.setWindowTitle("Brick Viewer " + config.BRICKV_VERSION)

        self.tree_view_model_labels = ['Name', 'UID', 'FW Version']
        self.tree_view_model = QStandardItemModel()
        self.tree_view.setModel(self.tree_view_model)
        self.tree_view.doubleClicked.connect(self.item_double_clicked)
        self.set_tree_view_defaults()

        # Remove dummy tab
        self.tab_widget.removeTab(1)
        self.last_tab = 0

        self.name = '<unknown>'
        self.uid = '<unknown>'
        self.version = (0, 0, 0)

        self.disconnect_times = []

        self.qtcb_enumerate.connect(self.cb_enumerate)
        self.qtcb_connected.connect(self.cb_connected)
        self.qtcb_disconnected.connect(self.cb_disconnected)

        self.ipcon = IPConnection()
        self.ipcon.set_auto_reauthenticate(False)
        self.ipcon.register_callback(IPConnection.CALLBACK_ENUMERATE,
                                     self.qtcb_enumerate.emit)
        self.ipcon.register_callback(IPConnection.CALLBACK_CONNECTED,
                                     self.qtcb_connected.emit)
        self.ipcon.register_callback(IPConnection.CALLBACK_DISCONNECTED,
                                     self.qtcb_disconnected.emit)

        self.flashing_window = None
        self.advanced_window = None
        self.delayed_refresh_updates_timer = QTimer()
        self.delayed_refresh_updates_timer.timeout.connect(self.delayed_refresh_updates)
        self.delayed_refresh_updates_timer.setInterval(500)
        self.reset_view()
        self.button_advanced.setDisabled(True)

        self.tab_widget.currentChanged.connect(self.tab_changed)
        self.button_connect.pressed.connect(self.connect_pressed)
        self.button_flashing.pressed.connect(self.flashing_pressed)
        self.button_advanced.pressed.connect(self.advanced_pressed)
        self.plugin_manager = PluginManager()

        self.combo_host.addItem(config.get_host())
        self.combo_host.addItems(config.get_host_history(HOST_HISTORY_SIZE - 1))
        self.spinbox_port.setValue(config.get_port())

        self.last_host = self.combo_host.currentText()
        self.last_port = self.spinbox_port.value()

        self.checkbox_authentication.stateChanged.connect(self.authentication_state_changed)

        self.label_secret.hide()

        self.edit_secret.hide()
        self.edit_secret.setEchoMode(QLineEdit.Password)

        self.checkbox_secret_show.hide()
        self.checkbox_secret_show.stateChanged.connect(self.secret_show_state_changed)

        self.checkbox_remember_secret.hide()

        if config.get_use_authentication():
            self.checkbox_authentication.setCheckState(Qt.Checked)

        if config.get_remember_secret():
            self.edit_secret.setText(config.get_secret())
            self.checkbox_remember_secret.setCheckState(Qt.Checked)

        self.label_auto_reconnects.hide()
        self.auto_reconnects = 0

    def closeEvent(self, event):
        self.exit_brickv()

    def exit_brickv(self, signl=None, frme=None):
        try:
            uid = self.tab_widget.widget(self.last_tab)._uid
            infos.infos[uid].plugin.stop()
        except:
            pass

        host = str(self.combo_host.currentText())
        history = []

        for i in range(self.combo_host.count()):
            h = str(self.combo_host.itemText(i))

            if h != host and h not in history:
                history.append(h)

        config.set_host(host)
        config.set_host_history(history[:HOST_HISTORY_SIZE - 1])
        config.set_port(self.spinbox_port.value())
        config.set_use_authentication(self.checkbox_authentication.isChecked())

        remember_secret = self.checkbox_remember_secret.isChecked()

        config.set_remember_secret(remember_secret)

        if remember_secret:
            config.set_secret(str(self.edit_secret.text()))
        else:
            config.set_secret(config.DEFAULT_SECRET)

        self.reset_view()

        try:
            self.ipcon.disconnect()
        except:
            pass

        if signl != None and frme != None:
            print "Received SIGINT or SIGTERM, shutting down."
            sys.exit()

    def start(self):
        pass

    def stop(self):
        pass

    def destroy(self):
        pass

    def authentication_state_changed(self, state):
        visible = state == Qt.Checked

        self.label_secret.setVisible(visible)
        self.edit_secret.setVisible(visible)
        self.checkbox_secret_show.setVisible(visible)
        self.checkbox_remember_secret.setVisible(visible)

    def secret_show_state_changed(self, state):
        if state == Qt.Checked:
            self.edit_secret.setEchoMode(QLineEdit.Normal)
        else:
            self.edit_secret.setEchoMode(QLineEdit.Password)

    def tab_changed(self, i):
        try:
            uid = self.tab_widget.widget(i)._uid
            infos.infos[uid].plugin.start()
        except:
            pass

        try:
            uid = self.tab_widget.widget(self.last_tab)._uid
            infos.infos[uid].plugin.stop()
        except:
            pass

        self.last_tab = i

    def reset_view(self):
        self.tab_widget.setCurrentIndex(0)

        keys_to_remove = []
        for key in infos.infos:
            if infos.infos[key].type in ('brick', 'bricklet'):
                try:
                    infos.infos[key].plugin.stop()
                except:
                    pass

                try:
                    infos.infos[key].plugin.destroy()
                except:
                    pass
                keys_to_remove.append(key)

        for key in keys_to_remove:
            try:
                infos.infos.pop(key)
            except:
                pass

        for i in reversed(range(1, self.tab_widget.count())):
            self.tab_widget.removeTab(i)

        self.update_tree_view()

    def do_disconnect(self):
        self.auto_reconnects = 0
        self.label_auto_reconnects.hide()

        self.reset_view()
        async_next_session()

        try:
            self.ipcon.disconnect()
        except:
            pass

    def do_authenticate(self, is_auto_reconnect):
        if not self.checkbox_authentication.isChecked():
            return True

        try:
            secret = str(self.edit_secret.text()).encode('ascii')
        except:
            self.do_disconnect()

            QMessageBox.critical(self, 'Connection',
                                 'Authentication secret cannot contain non-ASCII characters.',
                                 QMessageBox.Ok)
            return False

        self.ipcon.set_auto_reconnect(False) # don't auto-reconnect on authentication error

        try:
            self.ipcon.authenticate(secret)
        except:
            self.do_disconnect()

            if is_auto_reconnect:
                extra = ' after auto-reconnect'
            else:
                extra = ''

            QMessageBox.critical(self, 'Connection',
                                 'Could not authenticate' + extra + '. Check secret and ensure ' +
                                 'authentication for Brick Daemon is enabled.',
                                 QMessageBox.Ok)
            return False

        self.ipcon.set_auto_reconnect(True)

        return True

    def flashing_pressed(self):
        first = False

        if self.flashing_window is None:
            first = True
            self.flashing_window = FlashingWindow(self)

        self.update_flashing_window()
        self.flashing_window.show()
        self.flashing_window.refresh_updates_pressed()

    def advanced_pressed(self):
        if self.advanced_window is None:
            self.advanced_window = AdvancedWindow(self)

        self.update_advanced_window()
        self.advanced_window.show()

    def connect_pressed(self):
        if self.ipcon.get_connection_state() == IPConnection.CONNECTION_STATE_DISCONNECTED:
            try:
                self.last_host = self.combo_host.currentText()
                self.last_port = self.spinbox_port.value()
                self.button_connect.setDisabled(True)
                self.button_connect.setText("Connecting ...")
                self.button_connect.repaint()
                QApplication.processEvents()
                self.ipcon.connect(self.last_host, self.last_port)
            except:
                self.button_connect.setDisabled(False)
                self.button_connect.setText("Connect")
                QMessageBox.critical(self, 'Connection',
                                     'Could not connect. Please check host, check ' +
                                     'port and ensure that Brick Daemon is running.')
        else:
            self.do_disconnect()

    def item_double_clicked(self, index):
        text = str(index.data().toString())
        i = self.tab_for_uid(text)
        if i > 0:
            self.tab_widget.setCurrentIndex(i)

    def connected_uid_pressed(self, connected_uid):
        i = self.tab_for_uid(connected_uid)
        if i > 0:
            self.tab_widget.setCurrentIndex(i)

    def create_plugin_container(self, plugin, connected_uid, position):
        container = QWidget()
        container._uid = plugin.uid
        layout = QVBoxLayout(container)
        info = QHBoxLayout()

        # uid
        info.addWidget(QLabel('UID:'))
        label = QLabel('{0}'.format(plugin.uid))
        label.setTextInteractionFlags(Qt.TextSelectableByMouse |
                                      Qt.TextSelectableByKeyboard)
        info.addWidget(label)

        info.addSpacerItem(QSpacerItem(1, 1, QSizePolicy.Expanding))

        # connected uid
        if connected_uid != '0':
            info.addWidget(QLabel('Connected to:'))
            button = QToolButton()
            button.setText(connected_uid)
            button.pressed.connect(lambda: self.connected_uid_pressed(connected_uid))
            info.addWidget(button)

            info.addSpacerItem(QSpacerItem(1, 1, QSizePolicy.Expanding))

        # position
        info.addWidget(QLabel('Position:'))
        info.addWidget(QLabel('{0}'.format(position.upper())))

        info.addSpacerItem(QSpacerItem(1, 1, QSizePolicy.Expanding))

        # firmware version
        info.addWidget(QLabel('FW Version:'))
        info.addWidget(QLabel('{0}'.format(plugin.version_str)))

        info.addSpacerItem(QSpacerItem(1, 1, QSizePolicy.Expanding))

        # timeouts
        info.addWidget(QLabel('Timeouts:'))
        label_timeouts = QLabel('0')
        info.addWidget(label_timeouts)

        layout.addLayout(info)

        if plugin.is_brick():
            button = QPushButton('Reset')
            if plugin.has_reset_device():
                button.clicked.connect(plugin.reset_device)
            else:
                button.setDisabled(True)
            info.addSpacerItem(QSpacerItem(40, 20, QSizePolicy.Expanding))
            info.addWidget(button)

        line = QFrame()
        line.setFrameShape(QFrame.HLine)
        line.setFrameShadow(QFrame.Sunken)

        plugin.label_timeouts = label_timeouts
        plugin.layout().setContentsMargins(0, 0, 0, 0)

        layout.addWidget(line)
        layout.addWidget(plugin)

        return container

    def tab_for_uid(self, uid):
        for i in range(1, self.tab_widget.count()):
            try:
                widget = self.tab_widget.widget(i)
                if widget._uid == uid:
                    return i
            except:
                pass

        return -1

    def cb_enumerate(self, uid, connected_uid, position,
                     hardware_version, firmware_version,
                     device_identifier, enumeration_type):
        if self.ipcon.get_connection_state() != IPConnection.CONNECTION_STATE_CONNECTED:
            # ignore enumerate callbacks that arrived after the connection got closed
            return

        if enumeration_type in [IPConnection.ENUMERATION_TYPE_AVAILABLE,
                                IPConnection.ENUMERATION_TYPE_CONNECTED]:
            if device_identifier == BrickMaster.DEVICE_IDENTIFIER:
                info = infos.BrickMasterInfo()
            elif position in ('a', 'b', 'c', 'd', 'A', 'B', 'C', 'D'):
                position = position.lower()
                info = infos.BrickletInfo()
            else:
                info = infos.BrickInfo()

            if uid in infos.infos:
                info = infos.infos[uid]
            else:
                infos.infos[uid] = info

            for device in infos.infos.values():
                if device.type == 'brick':
                    if info.type == 'bricklet':
                        if device.uid == connected_uid:
                            device.bricklets[position] = info
                if device.type == 'bricklet':
                    if info.type == 'brick':
                        if uid == device.connected_uid:
                            info.bricklets[device.position] = device

            info.uid = uid
            info.connected_uid = connected_uid
            info.position = position
            info.hardware_version = hardware_version
            info.firmware_version_installed = firmware_version
            info.device_identifier = device_identifier
            info.protocol_version = 2
            info.enumeration_type = enumeration_type

            for device in infos.infos.values():
                if device.type in ('brick', 'bricklet'):
                    if device.uid == uid and device.plugin != None:
                        return

            plugin = self.plugin_manager.get_plugin(device_identifier, self.ipcon,
                                                    uid, firmware_version)

            if plugin is not None:
                info.plugin = plugin
                if plugin.is_hardware_version_relevant(hardware_version):
                    info.name = '{0} {1}.{2}'.format(plugin.name,
                                                     hardware_version[0],
                                                     hardware_version[1])
                else:
                    info.name = plugin.name

                info.url_part = plugin.get_url_part()

                c = self.create_plugin_container(plugin, connected_uid, position)
                info.plugin_container = c
                self.tab_widget.addTab(c, info.name)
        elif enumeration_type == IPConnection.ENUMERATION_TYPE_DISCONNECTED:
            for device_info in infos.infos.values():
                if device_info.type in ('brick', 'bricklet'):
                    if device_info.uid == uid:
                        try:
                            self.tab_widget.setCurrentIndex(0)
                            if device_info.plugin:
                                try:
                                    device_info.plugin.stop()
                                except:
                                    pass

                                try:
                                    device_info.plugin.destroy()
                                except:
                                    pass

                            i = self.tab_for_uid(device_info.uid)
                            self.tab_widget.removeTab(i)
                        except:
                            pass

                if device_info.type == 'brick':
                    for port in device_info.bricklets:
                        if device_info.bricklets[port]:
                            if device_info.bricklets[port].uid == uid:
                                device_info.bricklets[port] = None

                try:
                    infos.infos.pop(uid)
                except:
                    pass

        self.update_tree_view()

    def cb_connected(self, connect_reason):
        self.disconnect_times = []

        self.update_ui_state()

        if connect_reason == IPConnection.CONNECT_REASON_REQUEST:
            self.auto_reconnects = 0
            self.label_auto_reconnects.hide()

            self.ipcon.set_auto_reconnect(True)

            index = self.combo_host.findText(self.last_host)
            if index >= 0:
                self.combo_host.removeItem(index)
            self.combo_host.insertItem(-1, self.last_host)
            self.combo_host.setCurrentIndex(0)

            while self.combo_host.count() > HOST_HISTORY_SIZE:
                self.combo_host.removeItem(self.combo_host.count() - 1)

            if not self.do_authenticate(False):
                return

            try:
                self.ipcon.enumerate()
            except:
                self.update_ui_state()
        elif connect_reason == IPConnection.CONNECT_REASON_AUTO_RECONNECT:
            self.auto_reconnects += 1
            self.label_auto_reconnects.setText('Auto-Reconnect Count: {0}'.format(self.auto_reconnects))
            self.label_auto_reconnects.show()

            if not self.do_authenticate(True):
                return

            try:
                self.ipcon.enumerate()
            except:
                self.update_ui_state()
        else:
            try:
                self.ipcon.enumerate()
            except:
                self.update_ui_state()

    def cb_disconnected(self, disconnect_reason):
        if disconnect_reason == IPConnection.DISCONNECT_REASON_REQUEST:
            self.auto_reconnects = 0
            self.label_auto_reconnects.hide()

        if disconnect_reason == IPConnection.DISCONNECT_REASON_REQUEST or not self.ipcon.get_auto_reconnect():
            self.update_ui_state()
        elif len(self.disconnect_times) >= 3 and self.disconnect_times[-3] < time.time() + 1:
            self.disconnect_times = []
            self.ipcon.set_auto_reconnect(False)
            self.update_ui_state()
            self.reset_view()

            QMessageBox.critical(self, 'Connection',
                                 'Stopped automatic reconnecting due to multiple connection errors in a row.',
                                 QMessageBox.Ok)
        else:
            self.disconnect_times.append(time.time())
            self.update_ui_state(IPConnection.CONNECTION_STATE_PENDING)

    def set_tree_view_defaults(self):
        self.tree_view_model.setHorizontalHeaderLabels(self.tree_view_model_labels)
        self.tree_view.expandAll()
        self.tree_view.setColumnWidth(0, 260)
        self.tree_view.setColumnWidth(1, 75)
        self.tree_view.setColumnWidth(2, 85)
        self.tree_view.setSortingEnabled(True)
        self.tree_view.header().setSortIndicator(0, Qt.AscendingOrder)

    def update_ui_state(self, connection_state=None):
        # FIXME: need to call processEvents() otherwise get_connection_state()
        #        might return the wrong value
        QApplication.processEvents()

        if connection_state is None:
            connection_state = self.ipcon.get_connection_state()

        self.button_connect.setDisabled(False)
        self.button_flashing.setDisabled(False)

        if connection_state == IPConnection.CONNECTION_STATE_DISCONNECTED:
            self.button_connect.setText('Connect')
            self.combo_host.setDisabled(False)
            self.spinbox_port.setDisabled(False)
            self.checkbox_authentication.setDisabled(False)
            self.edit_secret.setDisabled(False)
            self.button_advanced.setDisabled(True)
        elif connection_state == IPConnection.CONNECTION_STATE_CONNECTED:
            self.button_connect.setText("Disconnect")
            self.combo_host.setDisabled(True)
            self.spinbox_port.setDisabled(True)
            self.checkbox_authentication.setDisabled(True)
            self.edit_secret.setDisabled(True)
            self.update_advanced_window(False)
        elif connection_state == IPConnection.CONNECTION_STATE_PENDING:
            self.button_connect.setText('Abort Pending Automatic Reconnect')
            self.combo_host.setDisabled(True)
            self.spinbox_port.setDisabled(True)
            self.checkbox_authentication.setDisabled(True)
            self.edit_secret.setDisabled(True)
            self.button_advanced.setDisabled(True)
            self.button_flashing.setDisabled(True)

        enable = connection_state == IPConnection.CONNECTION_STATE_CONNECTED
        for i in range(1, self.tab_widget.count()):
            self.tab_widget.setTabEnabled(i, enable)

        QApplication.processEvents()

    def update_tree_view(self):
        self.tree_view_model.clear()

        for device_info in sorted(infos.infos.values(), cmp=lambda x, y: cmp(x.name, y.name)):
            if device_info.type == 'brick':
                parent = [QStandardItem(device_info.name),
                          QStandardItem(device_info.uid),
                          QStandardItem('.'.join(map(str, device_info.firmware_version_installed)))]
                for item in parent:
                    item.setFlags(item.flags() & ~Qt.ItemIsEditable)

                self.tree_view_model.appendRow(parent)
                for port in sorted(device_info.bricklets):
                    if device_info.bricklets[port] and device_info.bricklets[port].protocol_version == 2:
                        child = [QStandardItem(port.upper() + ': ' +device_info.bricklets[port].name),
                                 QStandardItem(device_info.bricklets[port].uid),
                                 QStandardItem('.'.join(map(str, device_info.bricklets[port].firmware_version_installed)))]
                        for item in child:
                            item.setFlags(item.flags() & ~Qt.ItemIsEditable)
                        parent[0].appendRow(child)

        self.set_tree_view_defaults()
        self.update_flashing_window()
        self.update_advanced_window()
        self.delayed_refresh_updates_timer.start()

    def update_flashing_window(self):
        if self.flashing_window is not None:
            self.flashing_window.update_bricks()

    def update_advanced_window(self, update_window=True):
        has_brick = False

        for info in infos.infos.values():
            if info.type == 'brick':
                has_brick = True

        self.button_advanced.setEnabled(has_brick)

        if self.advanced_window is not None and update_window:
            self.advanced_window.update_bricks()

    def delayed_refresh_updates(self):
        self.delayed_refresh_updates_timer.stop()

        if self.flashing_window is not None and self.flashing_window.isVisible():
            self.flashing_window.refresh_updates_pressed()
Esempio n. 5
0
class MainWindow(QMainWindow, Ui_MainWindow):
    qtcb_enumerate = pyqtSignal(str, str, 'char', type((0,)), type((0,)), int, int)
    qtcb_connected = pyqtSignal(int)
    qtcb_disconnected = pyqtSignal(int)

    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)

        self.setupUi(self)

        signal.signal(signal.SIGINT, self.exit_brickv)
        signal.signal(signal.SIGTERM, self.exit_brickv)

        self.async_thread = async_start_thread(self)

        self.setWindowTitle("Brick Viewer " + config.BRICKV_VERSION)

        self.tree_view_model_labels = ['Name', 'UID', 'Position', 'FW Version']
        self.tree_view_model = QStandardItemModel(self)
        self.tree_view.setModel(self.tree_view_model)
        self.tree_view.doubleClicked.connect(self.item_double_clicked)
        self.set_tree_view_defaults()

        # Remove dummy tab
        self.tab_widget.removeTab(1)

        self.name = '<unknown>'
        self.uid = '<unknown>'
        self.version = (0, 0, 0)

        self.disconnect_times = []

        self.qtcb_enumerate.connect(self.cb_enumerate)
        self.qtcb_connected.connect(self.cb_connected)
        self.qtcb_disconnected.connect(self.cb_disconnected)

        self.ipcon = IPConnection()
        self.ipcon.register_callback(IPConnection.CALLBACK_ENUMERATE,
                                     self.qtcb_enumerate.emit)
        self.ipcon.register_callback(IPConnection.CALLBACK_CONNECTED,
                                     self.qtcb_connected.emit)
        self.ipcon.register_callback(IPConnection.CALLBACK_DISCONNECTED,
                                     self.qtcb_disconnected.emit)

        self.current_device_info = None
        self.flashing_window = None
        self.advanced_window = None
        self.delayed_refresh_updates_timer = QTimer()
        self.delayed_refresh_updates_timer.timeout.connect(self.delayed_refresh_updates)
        self.delayed_refresh_updates_timer.setInterval(500)
        self.reset_view()
        self.button_advanced.setDisabled(True)

        self.tab_widget.currentChanged.connect(self.tab_changed)
        self.tab_widget.setMovable(True)
        self.tab_widget.tabBar().installEventFilter(self)

        self.button_connect.clicked.connect(self.connect_clicked)
        self.button_flashing.clicked.connect(self.flashing_clicked)
        self.button_advanced.clicked.connect(self.advanced_clicked)
        self.plugin_manager = PluginManager()

        # host info
        self.host_infos = config.get_host_infos(config.HOST_INFO_COUNT)
        self.host_index_changing = True

        for host_info in self.host_infos:
            self.combo_host.addItem(host_info.host)

        self.last_host = None
        self.combo_host.currentIndexChanged.connect(self.host_index_changed)

        self.spinbox_port.setValue(self.host_infos[0].port)
        self.spinbox_port.valueChanged.connect(self.port_changed)

        self.checkbox_authentication.stateChanged.connect(self.authentication_state_changed)

        self.label_secret.hide()
        self.edit_secret.hide()
        self.edit_secret.setEchoMode(QLineEdit.Password)
        self.edit_secret.textEdited.connect(self.secret_changed)

        self.checkbox_secret_show.hide()
        self.checkbox_secret_show.stateChanged.connect(self.secret_show_state_changed)

        self.checkbox_remember_secret.hide()
        self.checkbox_remember_secret.stateChanged.connect(self.remember_secret_state_changed)

        self.checkbox_authentication.setChecked(self.host_infos[0].use_authentication)
        self.edit_secret.setText(self.host_infos[0].secret)
        self.checkbox_remember_secret.setChecked(self.host_infos[0].remember_secret)

        self.host_index_changing = False

        # auto-reconnect
        self.label_auto_reconnects.hide()
        self.auto_reconnects = 0

        # RED Session losts
        self.label_red_session_losts.hide()
        self.red_session_losts = 0

    # override QMainWindow.closeEvent
    def closeEvent(self, event):
        self.exit_brickv()

    def exit_brickv(self, signal=None, frame=None):
        if self.current_device_info is not None:
            self.current_device_info.plugin.stop_plugin()
            self.current_device_info.plugin.destroy_plugin()

        self.update_current_host_info()
        config.set_host_infos(self.host_infos)

        self.do_disconnect()

        if signal != None and frame != None:
            print("Received SIGINT or SIGTERM, shutting down.")
            sys.exit()

    def host_index_changed(self, i):
        if i < 0:
            return

        self.host_index_changing = True

        self.spinbox_port.setValue(self.host_infos[i].port)
        self.checkbox_authentication.setChecked(self.host_infos[i].use_authentication)
        self.edit_secret.setText(self.host_infos[i].secret)
        self.checkbox_remember_secret.setChecked(self.host_infos[i].remember_secret)

        self.host_index_changing = False

    def port_changed(self, value):
        self.update_current_host_info()

    def authentication_state_changed(self, state):
        visible = state == Qt.Checked

        self.label_secret.setVisible(visible)
        self.edit_secret.setVisible(visible)
        self.checkbox_secret_show.setVisible(visible)
        self.checkbox_remember_secret.setVisible(visible)

        self.update_current_host_info()

    def secret_changed(self):
        self.update_current_host_info()

    def secret_show_state_changed(self, state):
        if state == Qt.Checked:
            self.edit_secret.setEchoMode(QLineEdit.Normal)
        else:
            self.edit_secret.setEchoMode(QLineEdit.Password)

        self.update_current_host_info()

    def remember_secret_state_changed(self, state):
        self.update_current_host_info()

    def tab_changed(self, i):
        if not hasattr(self.tab_widget.widget(i), '_info'):
            new_current_device_info = None
        else:
            new_current_device_info = self.tab_widget.widget(i)._info
            new_current_device_info.plugin.start_plugin()

        # stop the now deselected plugin, if there is one that's running
        if self.current_device_info is not None:
            self.current_device_info.plugin.stop_plugin()

        self.current_device_info = new_current_device_info

    def update_current_host_info(self):
        if self.host_index_changing:
            return

        i = self.combo_host.currentIndex()

        if i < 0:
            return

        #self.host_infos[i].host = self.combo_host.currentText()
        self.host_infos[i].port = self.spinbox_port.value()
        self.host_infos[i].use_authentication = self.checkbox_authentication.isChecked()
        self.host_infos[i].secret = self.edit_secret.text()
        self.host_infos[i].remember_secret = self.checkbox_remember_secret.isChecked()

    def remove_all_device_infos(self):
        for device_info in infos.get_device_infos():
            self.remove_device_info(device_info.uid)

    def remove_device_info(self, uid):
        tab_id = self.tab_for_uid(uid)
        device_info = infos.get_info(uid)

        device_info.plugin.stop_plugin()
        device_info.plugin.destroy_plugin()

        if tab_id >= 0:
            self.tab_widget.removeTab(tab_id)

        # ensure that the widget gets correctly destroyed. otherwise QWidgets
        # tend to leak as Python is not able to collect their PyQt object
        tab_window = device_info.tab_window
        device_info.tab_window = None

        # If we reboot the RED Brick, the tab_window sometimes is
        # already None here
        if tab_window != None:
            tab_window.hide()
            tab_window.setParent(None)

        plugin = device_info.plugin
        device_info.plugin = None

        if plugin != None:
            plugin.hide()
            plugin.setParent(None)

        infos.remove_info(uid)

    def reset_view(self):
        self.tab_widget.setCurrentIndex(0)
        self.remove_all_device_infos()
        self.update_tree_view()

    def do_disconnect(self):
        self.auto_reconnects = 0
        self.label_auto_reconnects.hide()

        self.red_session_losts = 0
        self.label_red_session_losts.hide()

        self.reset_view()
        async_next_session()

        # force garbage collection, to ensure that all plugin related objects
        # got destroyed before disconnect is called. this is especially
        # important for the RED Brick plugin because its relies on releasing
        # the the RED Brick API objects in the __del__ method as a last resort
        # to avoid leaking object references. but this only works if garbage
        # collection is done before disconnect is called
        gc.collect()

        try:
            self.ipcon.disconnect()
        except:
            pass

    def do_authenticate(self, is_auto_reconnect):
        if not self.checkbox_authentication.isChecked():
            return True

        try:
            secret = self.edit_secret.text().encode('ascii')
        except:
            self.do_disconnect()

            QMessageBox.critical(self, 'Connection',
                                 'Authentication secret cannot contain non-ASCII characters.',
                                 QMessageBox.Ok)
            return False

        self.ipcon.set_auto_reconnect(False) # don't auto-reconnect on authentication error

        try:
            self.ipcon.authenticate(secret)
        except:
            self.do_disconnect()

            if is_auto_reconnect:
                extra = ' after auto-reconnect'
            else:
                extra = ''

            QMessageBox.critical(self, 'Connection',
                                 'Could not authenticate' + extra + '. Check secret and ensure ' +
                                 'authentication for Brick Daemon is enabled.',
                                 QMessageBox.Ok)
            return False

        self.ipcon.set_auto_reconnect(True)

        return True

    def flashing_clicked(self):
        if self.flashing_window is None:
            self.flashing_window = FlashingWindow(self)

        self.flashing_window.show()
        self.flashing_window.refresh_updates_clicked()

    def advanced_clicked(self):
        if self.advanced_window is None:
            self.advanced_window = AdvancedWindow(self)

        self.advanced_window.show()

    def connect_clicked(self):
        if self.ipcon.get_connection_state() == IPConnection.CONNECTION_STATE_DISCONNECTED:
            try:
                self.last_host = self.combo_host.currentText()
                self.button_connect.setDisabled(True)
                self.button_connect.setText("Connecting ...")
                self.button_connect.repaint()
                QApplication.processEvents()
                self.ipcon.connect(self.last_host, self.spinbox_port.value())
            except:
                self.button_connect.setDisabled(False)
                self.button_connect.setText("Connect")
                QMessageBox.critical(self, 'Connection',
                                     'Could not connect. Please check host, check ' +
                                     'port and ensure that Brick Daemon is running.')
        else:
            self.do_disconnect()

    def item_double_clicked(self, index):
        uid_index = index.sibling(index.row(), 1)

        if uid_index.isValid():
            uid_text = uid_index.data()
            self.show_plugin(uid_text)

    def create_tab_window(self, device_info, connected_uid, position):
        tab_window = TabWindow(self.tab_widget, device_info.name, self.untab)
        tab_window._info = device_info
        tab_window.set_callback_on_tab(lambda index:
            self.ipcon.get_connection_state() == IPConnection.CONNECTION_STATE_PENDING and \
                self.tab_widget.setTabEnabled(index, False))

        layout = QVBoxLayout(tab_window)
        info_bar = QHBoxLayout()

        # uid
        info_bar.addWidget(QLabel('UID:'))

        label = QLabel('{0}'.format(device_info.uid))
        label.setTextInteractionFlags(Qt.TextSelectableByMouse |
                                      Qt.TextSelectableByKeyboard)

        info_bar.addWidget(label)
        info_bar.addSpacerItem(QSpacerItem(1, 1, QSizePolicy.Expanding))

        # connected uid
        if connected_uid != '0':
            info_bar.addWidget(QLabel('Connected to:'))

            button = QToolButton()
            button.setText(connected_uid)
            button.clicked.connect(lambda: self.show_plugin(connected_uid))

            info_bar.addWidget(button)
            info_bar.addSpacerItem(QSpacerItem(1, 1, QSizePolicy.Expanding))

        # position
        info_bar.addWidget(QLabel('Position:'))
        info_bar.addWidget(QLabel('{0}'.format(position.upper())))

        info_bar.addSpacerItem(QSpacerItem(1, 1, QSizePolicy.Expanding))

        # firmware version
        label_version_name = QLabel('Version:')
        label_version = QLabel('...')

        if not device_info.plugin.has_custom_version(label_version_name, label_version):
            label_version_name.setText('FW Version:')
            label_version.setText(infos.get_version_string(device_info.plugin.firmware_version))

        info_bar.addWidget(label_version_name)
        info_bar.addWidget(label_version)

        info_bar.addSpacerItem(QSpacerItem(1, 1, QSizePolicy.Expanding))

        # timeouts
        info_bar.addWidget(QLabel('Timeouts:'))
        label_timeouts = QLabel('0')
        info_bar.addWidget(label_timeouts)

        layout.addLayout(info_bar)

        # actions
        actions = device_info.plugin.get_actions()

        if actions != None:
            if type(actions) == QAction:
                button = QPushButton(actions.text())
                button.clicked.connect(actions.trigger)
            else:
                button = QToolButton()
                button.setText(actions[0])
                button.setPopupMode(QToolButton.InstantPopup)
                button.setToolButtonStyle(Qt.ToolButtonTextOnly)
                button.setArrowType(Qt.DownArrow)
                button.setAutoRaise(True)

                menu = QMenu(actions[0])
                button.setMenu(menu)

                for action in actions[1]:
                    menu.addAction(action)

            info_bar.addSpacerItem(QSpacerItem(40, 20, QSizePolicy.Expanding))
            info_bar.addWidget(button)

        line = QFrame()
        line.setFrameShape(QFrame.HLine)
        line.setFrameShadow(QFrame.Sunken)

        device_info.plugin.label_timeouts = label_timeouts
        device_info.plugin.layout().setContentsMargins(0, 0, 0, 0)

        layout.addWidget(line)
        layout.addWidget(device_info.plugin)

        return tab_window

    def tab_move(self, event):
        # visualize rearranging of tabs (if allowed by tab_widget)
        if self.tab_widget.isMovable():
            if event.type() == QEvent.MouseButtonPress and event.button() & Qt.LeftButton:
                QApplication.setOverrideCursor(QCursor(Qt.SizeHorCursor))

            elif event.type() == QEvent.MouseButtonRelease and event.button() & Qt.LeftButton:
                QApplication.restoreOverrideCursor()

        return False

    def untab(self, tab_index):
        tab = self.tab_widget.widget(tab_index)
        tab.untab()
        tab._info.plugin.start_plugin()
        self.tab_widget.setCurrentIndex(0)

    def eventFilter(self, source, event):
        if source is self.tab_widget.tabBar():
            return self.tab_move(event)

        return False

    def tab_for_uid(self, uid):
        for i in range(1, self.tab_widget.count()):
            try:
                if self.tab_widget.widget(i)._info.uid == uid:
                    return i
            except:
                pass

        return -1

    def show_plugin(self, uid):
        i = self.tab_for_uid(uid)
        tab_window = infos.get_info(uid).tab_window

        if i > 0 and self.tab_widget.isTabEnabled(i):
            self.tab_widget.setCurrentIndex(i)

        QApplication.setActiveWindow(tab_window)

        tab_window.show()
        tab_window.activateWindow()
        tab_window.raise_()

    def cb_enumerate(self, uid, connected_uid, position,
                     hardware_version, firmware_version,
                     device_identifier, enumeration_type):
        if self.ipcon.get_connection_state() != IPConnection.CONNECTION_STATE_CONNECTED:
            # ignore enumerate callbacks that arrived after the connection got closed
            return

        if enumeration_type in [IPConnection.ENUMERATION_TYPE_AVAILABLE,
                                IPConnection.ENUMERATION_TYPE_CONNECTED]:
            device_info = infos.get_info(uid)
            something_changed_ref = [False]

            if device_info == None:
                if device_identifier == BrickMaster.DEVICE_IDENTIFIER:
                    device_info = infos.BrickMasterInfo()
                elif device_identifier == BrickRED.DEVICE_IDENTIFIER:
                    device_info = infos.BrickREDInfo()
                elif position in ('a', 'b', 'c', 'd', 'A', 'B', 'C', 'D'):
                    position = position.lower()
                    device_info = infos.BrickletInfo()
                else:
                    device_info = infos.BrickInfo()
                    something_changed_ref[0] = True

            def set_device_info_value(name, value):
                if getattr(device_info, name) != value:
                    setattr(device_info, name, value)
                    something_changed_ref[0] = True

            set_device_info_value('uid', uid)
            set_device_info_value('connected_uid', connected_uid)
            set_device_info_value('position', position)
            set_device_info_value('hardware_version', hardware_version)
            set_device_info_value('firmware_version_installed', firmware_version)
            set_device_info_value('device_identifier', device_identifier)
            set_device_info_value('protocol_version', 2)
            set_device_info_value('enumeration_type', enumeration_type)

            if device_info.type == 'bricklet':
                for brick_info in infos.get_brick_infos():
                    if brick_info.uid == device_info.connected_uid:
                        if brick_info.bricklets[position] != device_info:
                            brick_info.bricklets[position] = device_info
                            something_changed_ref[0] = True
            elif device_info.type == 'brick':
                for bricklet_info in infos.get_bricklet_infos():
                    if bricklet_info.connected_uid == device_info.uid:
                        if device_info.bricklets[bricklet_info.position] != bricklet_info:
                            device_info.bricklets[bricklet_info.position] = bricklet_info
                            something_changed_ref[0] = True

            if device_info.plugin == None:
                plugin = self.plugin_manager.get_plugin(device_identifier, self.ipcon,
                                                        uid, hardware_version, firmware_version)

                device_info.plugin = plugin
                device_info.name = plugin.name
                device_info.url_part = plugin.get_url_part()

                infos.add_info(device_info)

                device_info.tab_window = self.create_tab_window(device_info, connected_uid, position)
                device_info.tab_window.setWindowFlags(Qt.Widget)
                device_info.tab_window.tab()

                something_changed_ref[0] = True

            if something_changed_ref[0]:
                self.update_tree_view()
        elif enumeration_type == IPConnection.ENUMERATION_TYPE_DISCONNECTED:
            for device_info in infos.get_device_infos():
                if device_info.uid == uid:
                    self.tab_widget.setCurrentIndex(0)
                    self.remove_device_info(device_info.uid)

                if device_info.type == 'brick':
                    for port in device_info.bricklets:
                        if device_info.bricklets[port] and device_info.bricklets[port].uid == uid:
                            device_info.bricklets[port] = None

            self.update_tree_view()

    def hack_to_remove_red_brick_tab(self, red_brick_uid):
        for device_info in infos.get_device_infos():
            if device_info.uid == red_brick_uid:
                self.tab_widget.setCurrentIndex(0)
                self.remove_device_info(device_info.uid)

                self.red_session_losts += 1
                self.label_red_session_losts.setText('RED Brick Session Loss Count: {0}'.format(self.red_session_losts))
                self.label_red_session_losts.show()

                break

        self.update_tree_view()

    def cb_connected(self, connect_reason):
        self.disconnect_times = []

        self.update_ui_state()

        if connect_reason == IPConnection.CONNECT_REASON_REQUEST:
            self.auto_reconnects = 0
            self.label_auto_reconnects.hide()

            self.red_session_losts = 0
            self.label_red_session_losts.hide()

            self.ipcon.set_auto_reconnect(True)

            index = self.combo_host.findText(self.last_host)

            if index >= 0:
                self.combo_host.removeItem(index)

                host_info = self.host_infos[index]

                del self.host_infos[index]
                self.host_infos.insert(0, host_info)
            else:
                index = self.combo_host.currentIndex()

                host_info = self.host_infos[index].duplicate()
                host_info.host = self.last_host

                self.host_infos.insert(0, host_info)

            self.combo_host.insertItem(-1, self.last_host)
            self.combo_host.setCurrentIndex(0)

            while self.combo_host.count() > config.HOST_INFO_COUNT:
                self.combo_host.removeItem(self.combo_host.count() - 1)

            if not self.do_authenticate(False):
                return

            try:
                self.ipcon.enumerate()
            except:
                self.update_ui_state()
        elif connect_reason == IPConnection.CONNECT_REASON_AUTO_RECONNECT:
            self.auto_reconnects += 1
            self.label_auto_reconnects.setText('Auto-Reconnect Count: {0}'.format(self.auto_reconnects))
            self.label_auto_reconnects.show()

            if not self.do_authenticate(True):
                return

            try:
                self.ipcon.enumerate()
            except:
                self.update_ui_state()
        else:
            try:
                self.ipcon.enumerate()
            except:
                self.update_ui_state()

    def cb_disconnected(self, disconnect_reason):
        if disconnect_reason == IPConnection.DISCONNECT_REASON_REQUEST:
            self.auto_reconnects = 0
            self.label_auto_reconnects.hide()

            self.red_session_losts = 0
            self.label_red_session_losts.hide()

        if disconnect_reason == IPConnection.DISCONNECT_REASON_REQUEST or not self.ipcon.get_auto_reconnect():
            self.update_ui_state()
        elif len(self.disconnect_times) >= 3 and self.disconnect_times[-3] < time.time() + 1:
            self.disconnect_times = []
            self.ipcon.set_auto_reconnect(False)
            self.update_ui_state()
            self.reset_view()

            QMessageBox.critical(self, 'Connection',
                                 'Stopped automatic reconnecting due to multiple connection errors in a row.')
        else:
            self.disconnect_times.append(time.time())
            self.update_ui_state(IPConnection.CONNECTION_STATE_PENDING)

    def set_tree_view_defaults(self):
        self.tree_view_model.setHorizontalHeaderLabels(self.tree_view_model_labels)
        self.tree_view.expandAll()
        self.tree_view.setColumnWidth(0, 250)
        self.tree_view.setColumnWidth(1, 85)
        self.tree_view.setColumnWidth(2, 85)
        self.tree_view.setColumnWidth(3, 90)
        self.tree_view.setExpandsOnDoubleClick(False)
        self.tree_view.setSortingEnabled(True)
        self.tree_view.header().setSortIndicator(0, Qt.AscendingOrder)

    def update_ui_state(self, connection_state=None):
        # FIXME: need to call processEvents() otherwise get_connection_state()
        #        might return the wrong value
        QApplication.processEvents()

        if connection_state is None:
            connection_state = self.ipcon.get_connection_state()

        self.button_connect.setDisabled(False)
        self.button_flashing.setDisabled(False)

        if connection_state == IPConnection.CONNECTION_STATE_DISCONNECTED:
            self.button_connect.setText('Connect')
            self.combo_host.setDisabled(False)
            self.spinbox_port.setDisabled(False)
            self.checkbox_authentication.setDisabled(False)
            self.edit_secret.setDisabled(False)
            self.button_advanced.setDisabled(True)
        elif connection_state == IPConnection.CONNECTION_STATE_CONNECTED:
            self.button_connect.setText("Disconnect")
            self.combo_host.setDisabled(True)
            self.spinbox_port.setDisabled(True)
            self.checkbox_authentication.setDisabled(True)
            self.edit_secret.setDisabled(True)
            self.update_advanced_window()

            # restart all pause plugins
            for info in infos.get_device_infos():
                info.plugin.resume_plugin()
        elif connection_state == IPConnection.CONNECTION_STATE_PENDING:
            self.button_connect.setText('Abort Pending Automatic Reconnect')
            self.combo_host.setDisabled(True)
            self.spinbox_port.setDisabled(True)
            self.checkbox_authentication.setDisabled(True)
            self.edit_secret.setDisabled(True)
            self.button_advanced.setDisabled(True)
            self.button_flashing.setDisabled(True)

            # pause all running plugins
            for info in infos.get_device_infos():
                info.plugin.pause_plugin()

        enable = connection_state == IPConnection.CONNECTION_STATE_CONNECTED

        for i in range(1, self.tab_widget.count()):
            self.tab_widget.setTabEnabled(i, enable)

        for device_info in infos.get_device_infos():
            device_info.tab_window.setEnabled(enable)

        QApplication.processEvents()

    def update_tree_view(self):
        self.tree_view_model.clear()

        for info in infos.get_brick_infos():
            parent = [QStandardItem(info.name),
                      QStandardItem(info.uid),
                      QStandardItem(info.position.upper()),
                      QStandardItem('.'.join(map(str, info.firmware_version_installed)))]

            for item in parent:
                item.setFlags(item.flags() & ~Qt.ItemIsEditable)

            self.tree_view_model.appendRow(parent)

            for port in sorted(info.bricklets):
                if info.bricklets[port] and info.bricklets[port].protocol_version == 2:
                    child = [QStandardItem(port.upper() + ': ' + info.bricklets[port].name),
                             QStandardItem(info.bricklets[port].uid),
                             QStandardItem(info.bricklets[port].position.upper()),
                             QStandardItem('.'.join(map(str, info.bricklets[port].firmware_version_installed)))]
                    for item in child:
                        item.setFlags(item.flags() & ~Qt.ItemIsEditable)
                    parent[0].appendRow(child)

        self.set_tree_view_defaults()
        self.update_advanced_window()
        self.delayed_refresh_updates_timer.start()

    def update_advanced_window(self):
        self.button_advanced.setEnabled(len(infos.get_brick_infos()) > 0)

    def delayed_refresh_updates(self):
        self.delayed_refresh_updates_timer.stop()

        if self.flashing_window is not None and self.flashing_window.isVisible():
            self.flashing_window.refresh_updates_clicked()
Esempio n. 6
0
class DataLogger(threading.Thread):
    """
    This class represents the data logger and an object of this class is
    the actual instance of a logging process
    """

    # constructor and other functions
    def __init__(self, config, gui_job=None):
        """
            config -- brickv.data_logger.configuration_validator.Configuration
        """
        super(DataLogger, self).__init__()

        self.jobs = []  # thread hashmap for all running threads/jobs
        self.job_exit_flag = False  # flag for stopping the thread
        self.job_sleep = 1  # TODO: Enahncement -> use condition objects
        self.timers = []
        self._gui_job = gui_job
        self.max_file_size = None
        self.max_file_count = None
        self.data_queue = {}  # universal data_queue hash map
        self.host = config._general[ConfigurationReader.GENERAL_HOST]
        self.port = utils.Utilities.parse_to_int(config._general[ConfigurationReader.GENERAL_PORT])
        self.ipcon = IPConnection()
        try:
            self.ipcon.connect(self.host, self.port)  # Connect to brickd
        except Exception as e:
            EventLogger.critical("A critical error occur: " + str(e))
            self.ipcon = None
            raise DataLoggerException(DataLoggerException.DL_CRITICAL_ERROR, "A critical error occur: " + str(e))

        EventLogger.info("Connection to " + self.host + ":" + str(self.port) + " established.")
        self.ipcon.set_timeout(1)  # TODO: Timeout number
        EventLogger.debug("Set ipcon.time_out to 1.")
        self._configuration = config
        self.default_file_path = "logged_data.csv"
        self.log_to_file = True
        self.log_to_xively = False
        self.stopped = False


    def process_general_section(self):
        """
        Information out of the general section will be consumed here
        """
        data = self._configuration._general

        self.log_to_file = data[ConfigurationReader.GENERAL_LOG_TO_FILE]
        self.default_file_path = data[ConfigurationReader.GENERAL_PATH_TO_FILE]
        self.max_file_size = data[ConfigurationReader.GENERAL_LOG_FILE_SIZE]
        self.max_file_count = data[ConfigurationReader.GENERAL_LOG_COUNT]

        EventLogger.debug("Logging output to file: " + str(self.log_to_file))
        EventLogger.debug("Output file path: " + str(self.default_file_path))

    def process_xively_section(self):
        """
        Information out of the xively section will be consumed here
        """
        data = self._configuration._xively
        # TODO: write code for xively handling
        if len(data) == 0:
            return

        self.log_to_xively = data.get(ConfigurationReader.XIVELY_ACTIVE)
        logging.debug("Logging output to Xively: " + str(self.log_to_xively))
        # = data.get(XIVELY_AGENT_DESCRIPTION)
        # = data.get(XIVELY_FEED)
        # = data.get(XIVELY_API_KEY)
        # = DataLogger.parse_to_int(data.get(XIVELY_UPDATE_RATE))

    def initialize_loggable_devices(self):
        """
        This function creates the actual objects for each device out of the configuration
        """
        device_list = self._configuration._devices

        wrong_uid_msg = "The following Devices got an invalid UID: "
        got_wrong_uid_exception = False

        # start the timers
        try:
            for i in range(0, len(device_list)):
                try:
                    loggable_devices.DeviceImpl(device_list[i], self).start_timer()

                except Exception as e:
                    if str(e) == "substring not found":
                        got_wrong_uid_exception = True
                        wrong_uid_msg += str(device_list[i][Idf.DD_NAME])+"("+str(device_list[i][Idf.DD_UID])+"), "
                    else:
                        # other important exception -> raise
                        raise e

            if got_wrong_uid_exception:
                raise Exception(wrong_uid_msg)

        except Exception as exc:
            msg = "A critical error occur: " + str(exc)
            self.stop()
            raise DataLoggerException(DataLoggerException.DL_CRITICAL_ERROR, msg)

    def run(self):
        """
        This function starts the actual logging process in a new thread
        """
        self.stopped = False
        self.process_general_section()
        self.process_xively_section()

        self.initialize_loggable_devices()

        """START-WRITE-THREAD"""
        # create jobs
        # look which thread should be working
        if self.log_to_file:
            self.jobs.append(CSVWriterJob(name="CSV-Writer", datalogger=self))
        if self.log_to_xively:
            self.jobs.append(XivelyJob(name="Xively-Writer", datalogger=self))
        if self._gui_job is not None:
            self._gui_job.set_datalogger(self)
            self.jobs.append(self._gui_job)

        for t in self.jobs:
            t.start()
        EventLogger.debug("Jobs started.")

        """START-TIMERS"""
        for t in self.timers:
            t.start()
        EventLogger.debug("Get-Timers started.")

        """END_CONDITIONS"""
        EventLogger.info("DataLogger is runninng...")
        # TODO Exit condition ?

    def stop(self):
        """
        This function ends the logging process. self.stopped will be set to True if
        the data logger stops
        """
        EventLogger.info("Closing Timers and Threads...")

        """CLEANUP_AFTER_STOP """
        # check if all timers stopped
        for t in self.timers:
            t.stop()
        for t in self.timers:
            t.join()
        EventLogger.debug("Get-Timers[" + str(len(self.timers)) + "] stopped.")

        # set THREAD_EXIT_FLAG for all work threads
        for job in self.jobs:
            job.stop()
        # wait for all threads to stop
        for job in self.jobs:
            job.join()
        EventLogger.debug("Jobs[" + str(len(self.jobs)) + "] stopped.")

        if self.ipcon is not None and self.ipcon.get_connection_state() == IPConnection.CONNECTION_STATE_CONNECTED:
            self.ipcon.disconnect()
        EventLogger.info("Connection closed successfully.")

        self.stopped = True

    def add_to_queue(self, csv):
        """
        Adds logged data to all queues which are registered in 'self.data_queue'

        csv --
        """
        for q in self.data_queue.values():
            q.put(csv)
Esempio n. 7
0
class DeviceDialog(QDialog, Ui_DeviceDialog):
    """
        Function and Event handling class for the Ui_DeviceDialog.
    """
    qtcb_enumerate = pyqtSignal(str, str, 'char', type((0,)), type((0,)), int, int)
    qtcb_connected = pyqtSignal(int)

    def __init__(self, parent):
        QDialog.__init__(self, parent, get_modeless_dialog_flags())

        self._logger_window = parent

        self.qtcb_enumerate.connect(self.cb_enumerate)
        self.qtcb_connected.connect(self.cb_connected)

        self.host = None
        self.port = None
        self.secret = None

        self.ipcon = IPConnection()
        self.ipcon.register_callback(IPConnection.CALLBACK_CONNECTED,
                                     self.qtcb_connected.emit)
        self.ipcon.register_callback(IPConnection.CALLBACK_ENUMERATE,
                                     self.qtcb_enumerate.emit)

        self.setupUi(self)

        self.btn_add_device.clicked.connect(self.btn_add_device_clicked)
        self.btn_refresh.clicked.connect(self.btn_refresh_clicked)
        self.btn_close.clicked.connect(self.btn_close_clicked)
        self.tree_widget.itemActivated.connect(self.add_item)

        self.connected_uids = []
        self.available_item = QTreeWidgetItem(['No devices available'])
        self.supported_item = QTreeWidgetItem(['Supported devices'])

        self.tree_widget.addTopLevelItem(self.available_item)
        self.tree_widget.addTopLevelItem(self.supported_item)

        for device_name in device_specs:
            self.supported_item.addChild(QTreeWidgetItem([device_name]))

        self.supported_item.sortChildren(0, Qt.AscendingOrder)
        self.supported_item.setExpanded(True)

    def cb_connected(self, connect_reason):
        self.tree_widget.clearSelection()
        self.available_item.takeChildren()
        self.available_item.setExpanded(True)
        self.available_item.setText(0, 'No devices available at {0}:{1}'.format(self.host, self.port))

        self.connected_uids = []

        if self.secret != None:
            self.ipcon.set_auto_reconnect(False) # don't auto-reconnect on authentication error

            try:
                self.ipcon.authenticate(self.secret)
            except:
                try:
                    self.ipcon.disconnect()
                except:
                    pass

                if connect_reason == IPConnection.CONNECT_REASON_AUTO_RECONNECT:
                    extra = ' after auto-reconnect'
                else:
                    extra = ''

                self.available_item.setText(0, 'Could not authenticate' + extra)
                return

            self.ipcon.set_auto_reconnect(True)

        try:
            self.ipcon.enumerate()
        except:
            pass

    def cb_enumerate(self, uid, connected_uid, position,
                     hardware_version, firmware_version,
                     device_identifier, enumeration_type):
        if enumeration_type in [IPConnection.ENUMERATION_TYPE_AVAILABLE,
                                IPConnection.ENUMERATION_TYPE_CONNECTED]:
            if uid not in self.connected_uids:
                try:
                    display_name = device_factory.get_device_display_name(device_identifier)
                except KeyError:
                    return # unknown device identifier

                if display_name in device_specs:
                    self.connected_uids.append(uid)
                    self.available_item.addChild(QTreeWidgetItem(['{0} [{1}]'.format(display_name, uid)]))
                    self.available_item.setText(0, 'Devices available at {0}:{1}'.format(self.host, self.port))
                    self.available_item.sortChildren(0, Qt.AscendingOrder)
        else:
            if uid in self.connected_uids:
                self.connected_uids.remove(uid)

            for i in range(self.available_item.childCount()):
                child = self.available_item.child(i)

                if '[{0}]'.format(uid) in child.text(0):
                    self.available_item.takeChild(i)
                    break

            if self.available_item.childCount() == 0:
                self.available_item.setText(0, 'No devices available at {0}:{1}'.format(self.host, self.port))

    def btn_add_device_clicked(self):
        for item in self.tree_widget.selectedItems():
            if item == self.available_item or item == self.supported_item:
                continue

            self._logger_window.add_device_to_tree(self.create_device_config(item.text(0)))

    def btn_refresh_clicked(self):
        try:
            self.ipcon.disconnect()
        except:
            pass

        self.tree_widget.clearSelection()
        self.available_item.takeChildren()
        self.available_item.setExpanded(True)

        self.connected_uids = []
        self.host = self._logger_window.combo_host.currentText()
        self.port = self._logger_window.spin_port.value()

        if self._logger_window.check_authentication.isChecked():
            try:
                self.secret = self._logger_window.edit_secret.text().encode('ascii')
            except:
                self.secret = None
        else:
            self.secret = None

        try:
            self.ipcon.connect(self.host, self.port)
            self.available_item.setText(0, 'No devices available at {0}:{1}'.format(self.host, self.port))
        except:
            self.available_item.setText(0, 'Could not connect to {0}:{1}'.format(self.host, self.port))

    def btn_close_clicked(self):
        self.close()

    def add_item(self, item):
        if item == self.available_item or item == self.supported_item:
            return

        self._logger_window.add_device_to_tree(self.create_device_config(item.text(0)))

    def create_device_config(self, item_text):
        name, uid = Utilities.parse_device_name(item_text) # FIXME
        device_spec = device_specs[name]

        if uid == None: # FIXME
            uid = ''

        device = {
            'host': 'default',
            'name': name,
            'uid': uid,
            'values': {},
            'options': {}
        }

        for value_spec in device_spec['values']:
            device['values'][value_spec['name']] = {'interval': 0}

            if value_spec['subvalues'] != None:
                device['values'][value_spec['name']]['subvalues'] = {}

                for subvalue_name in value_spec['subvalues']:
                    device['values'][value_spec['name']]['subvalues'][subvalue_name] = True

        if device_spec['options'] != None:
            for option_spec in device_spec['options']:
                device['options'][option_spec['name']] = {'value': option_spec['default']}

        return device
Esempio n. 8
0
class MainWindow(QMainWindow, Ui_MainWindow):
    qtcb_enumerate = pyqtSignal(str, str, 'char', type((0, )), type((0, )),
                                int, int)
    qtcb_connected = pyqtSignal(int)
    qtcb_disconnected = pyqtSignal(int)

    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)

        self.setupUi(self)

        signal.signal(signal.SIGINT, self.exit_brickv)
        signal.signal(signal.SIGTERM, self.exit_brickv)

        self.async_thread = async_start_thread(self)

        self.setWindowTitle("Brick Viewer " + config.BRICKV_VERSION)

        self.tree_view_model_labels = ['Name', 'UID', 'Position', 'FW Version']
        self.tree_view_model = QStandardItemModel(self)
        self.tree_view.setModel(self.tree_view_model)
        self.tree_view.doubleClicked.connect(self.item_double_clicked)
        self.set_tree_view_defaults()

        # Remove dummy tab
        self.tab_widget.removeTab(1)

        self.name = '<unknown>'
        self.uid = '<unknown>'
        self.version = (0, 0, 0)

        self.disconnect_times = []

        self.qtcb_enumerate.connect(self.cb_enumerate)
        self.qtcb_connected.connect(self.cb_connected)
        self.qtcb_disconnected.connect(self.cb_disconnected)

        self.ipcon = IPConnection()
        self.ipcon.register_callback(IPConnection.CALLBACK_ENUMERATE,
                                     self.qtcb_enumerate.emit)
        self.ipcon.register_callback(IPConnection.CALLBACK_CONNECTED,
                                     self.qtcb_connected.emit)
        self.ipcon.register_callback(IPConnection.CALLBACK_DISCONNECTED,
                                     self.qtcb_disconnected.emit)

        self.current_device_info = None
        self.flashing_window = None
        self.advanced_window = None
        self.data_logger_window = None
        self.delayed_refresh_updates_timer = QTimer()
        self.delayed_refresh_updates_timer.timeout.connect(
            self.delayed_refresh_updates)
        self.delayed_refresh_updates_timer.setInterval(500)
        self.reset_view()
        self.button_advanced.setDisabled(True)

        self.tab_widget.currentChanged.connect(self.tab_changed)
        self.tab_widget.setMovable(True)
        self.tab_widget.tabBar().installEventFilter(self)

        self.button_connect.clicked.connect(self.connect_clicked)
        self.button_flashing.clicked.connect(self.flashing_clicked)
        self.button_advanced.clicked.connect(self.advanced_clicked)
        self.button_data_logger.clicked.connect(self.data_logger_clicked)
        self.plugin_manager = PluginManager()

        # host info
        self.host_infos = config.get_host_infos(config.HOST_INFO_COUNT)
        self.host_index_changing = True

        for host_info in self.host_infos:
            self.combo_host.addItem(host_info.host)

        self.last_host = None
        self.combo_host.currentIndexChanged.connect(self.host_index_changed)

        self.spinbox_port.setValue(self.host_infos[0].port)
        self.spinbox_port.valueChanged.connect(self.port_changed)

        self.checkbox_authentication.stateChanged.connect(
            self.authentication_state_changed)

        self.label_secret.hide()
        self.edit_secret.hide()
        self.edit_secret.setEchoMode(QLineEdit.Password)
        self.edit_secret.textEdited.connect(self.secret_changed)

        self.checkbox_secret_show.hide()
        self.checkbox_secret_show.stateChanged.connect(
            self.secret_show_state_changed)

        self.checkbox_remember_secret.hide()
        self.checkbox_remember_secret.stateChanged.connect(
            self.remember_secret_state_changed)

        self.checkbox_authentication.setChecked(
            self.host_infos[0].use_authentication)
        self.edit_secret.setText(self.host_infos[0].secret)
        self.checkbox_remember_secret.setChecked(
            self.host_infos[0].remember_secret)

        self.host_index_changing = False

        # auto-reconnect
        self.label_auto_reconnects.hide()
        self.auto_reconnects = 0

        # RED Session losts
        self.label_red_session_losts.hide()
        self.red_session_losts = 0

    # override QMainWindow.closeEvent
    def closeEvent(self, event):
        if not self.exit_logger():
            event.ignore()
            return

        self.exit_brickv()

    def exit_brickv(self, signl=None, frme=None):
        if self.current_device_info is not None:
            self.current_device_info.plugin.stop_plugin()
            self.current_device_info.plugin.destroy_plugin()

        self.update_current_host_info()
        config.set_host_infos(self.host_infos)

        self.do_disconnect()

        if signl != None and frme != None:
            print("Received SIGINT or SIGTERM, shutting down.")
            sys.exit()

    def exit_logger(self):
        exitBrickv = True
        if (self.data_logger_window is not None) and (
                self.data_logger_window.data_logger_thread is not None) and (
                    not self.data_logger_window.data_logger_thread.stopped):
            quit_msg = "The Data Logger is running. Are you sure you want to exit the program?"
            reply = QMessageBox.question(self, 'Message', quit_msg,
                                         QMessageBox.Yes, QMessageBox.No)

            if reply == QMessageBox.Yes:
                self.data_logger_window.data_logger_thread.stop()
            else:
                exitBrickv = False

        return exitBrickv

    def host_index_changed(self, i):
        if i < 0:
            return

        self.host_index_changing = True

        self.spinbox_port.setValue(self.host_infos[i].port)
        self.checkbox_authentication.setChecked(
            self.host_infos[i].use_authentication)
        self.edit_secret.setText(self.host_infos[i].secret)
        self.checkbox_remember_secret.setChecked(
            self.host_infos[i].remember_secret)

        self.host_index_changing = False

    def port_changed(self, value):
        self.update_current_host_info()

    def authentication_state_changed(self, state):
        visible = state == Qt.Checked

        self.label_secret.setVisible(visible)
        self.edit_secret.setVisible(visible)
        self.checkbox_secret_show.setVisible(visible)
        self.checkbox_remember_secret.setVisible(visible)

        self.update_current_host_info()

    def secret_changed(self):
        self.update_current_host_info()

    def secret_show_state_changed(self, state):
        if state == Qt.Checked:
            self.edit_secret.setEchoMode(QLineEdit.Normal)
        else:
            self.edit_secret.setEchoMode(QLineEdit.Password)

        self.update_current_host_info()

    def remember_secret_state_changed(self, state):
        self.update_current_host_info()

    def tab_changed(self, i):
        if not hasattr(self.tab_widget.widget(i), '_info'):
            new_current_device_info = None
        else:
            new_current_device_info = self.tab_widget.widget(i)._info
            new_current_device_info.plugin.start_plugin()

        # stop the now deselected plugin, if there is one that's running
        if self.current_device_info is not None:
            self.current_device_info.plugin.stop_plugin()

        self.current_device_info = new_current_device_info

    def update_current_host_info(self):
        if self.host_index_changing:
            return

        i = self.combo_host.currentIndex()

        if i < 0:
            return

        #self.host_infos[i].host = self.combo_host.currentText()
        self.host_infos[i].port = self.spinbox_port.value()
        self.host_infos[
            i].use_authentication = self.checkbox_authentication.isChecked()
        self.host_infos[i].secret = self.edit_secret.text()
        self.host_infos[
            i].remember_secret = self.checkbox_remember_secret.isChecked()

    def remove_all_device_infos(self):
        for device_info in infos.get_device_infos():
            self.remove_device_info(device_info.uid)

    def remove_device_info(self, uid):
        tab_id = self.tab_for_uid(uid)
        device_info = infos.get_info(uid)

        device_info.plugin.stop_plugin()
        device_info.plugin.destroy_plugin()

        if tab_id >= 0:
            self.tab_widget.removeTab(tab_id)

        # ensure that the widget gets correctly destroyed. otherwise QWidgets
        # tend to leak as Python is not able to collect their PyQt object
        tab_window = device_info.tab_window
        device_info.tab_window = None

        # If we reboot the RED Brick, the tab_window sometimes is
        # already None here
        if tab_window != None:
            tab_window.hide()
            tab_window.setParent(None)

        plugin = device_info.plugin
        device_info.plugin = None

        if plugin != None:
            plugin.hide()
            plugin.setParent(None)

        infos.remove_info(uid)

    def reset_view(self):
        self.tab_widget.setCurrentIndex(0)
        self.remove_all_device_infos()
        self.update_tree_view()

    def do_disconnect(self):
        self.auto_reconnects = 0
        self.label_auto_reconnects.hide()

        self.red_session_losts = 0
        self.label_red_session_losts.hide()

        self.reset_view()
        async_next_session()

        # force garbage collection, to ensure that all plugin related objects
        # got destroyed before disconnect is called. this is especially
        # important for the RED Brick plugin because its relies on releasing
        # the the RED Brick API objects in the __del__ method as a last resort
        # to avoid leaking object references. but this only works if garbage
        # collection is done before disconnect is called
        gc.collect()

        try:
            self.ipcon.disconnect()
        except:
            pass

    def do_authenticate(self, is_auto_reconnect):
        if not self.checkbox_authentication.isChecked():
            return True

        try:
            secret = self.edit_secret.text().encode('ascii')
        except:
            self.do_disconnect()

            QMessageBox.critical(
                self, 'Connection',
                'Authentication secret cannot contain non-ASCII characters.',
                QMessageBox.Ok)
            return False

        self.ipcon.set_auto_reconnect(
            False)  # don't auto-reconnect on authentication error

        try:
            self.ipcon.authenticate(secret)
        except:
            self.do_disconnect()

            if is_auto_reconnect:
                extra = ' after auto-reconnect'
            else:
                extra = ''

            QMessageBox.critical(
                self, 'Connection', 'Could not authenticate' + extra +
                '. Check secret and ensure ' +
                'authentication for Brick Daemon is enabled.', QMessageBox.Ok)
            return False

        self.ipcon.set_auto_reconnect(True)

        return True

    def flashing_clicked(self):
        if self.flashing_window is None:
            self.flashing_window = FlashingWindow(self)

        self.flashing_window.show()
        self.flashing_window.refresh_updates_clicked()

    def advanced_clicked(self):
        if self.advanced_window is None:
            self.advanced_window = AdvancedWindow(self)

        self.advanced_window.show()

    def data_logger_clicked(self):
        if self.data_logger_window is None:
            self.data_logger_window = DataLoggerWindow(self)

        self.data_logger_window.show()

    def connect_clicked(self):
        if self.ipcon.get_connection_state(
        ) == IPConnection.CONNECTION_STATE_DISCONNECTED:
            try:
                self.last_host = self.combo_host.currentText()
                self.button_connect.setDisabled(True)
                self.button_connect.setText("Connecting ...")
                self.button_connect.repaint()
                QApplication.processEvents()
                self.ipcon.connect(self.last_host, self.spinbox_port.value())
            except:
                self.button_connect.setDisabled(False)
                self.button_connect.setText("Connect")
                QMessageBox.critical(
                    self, 'Connection',
                    'Could not connect. Please check host, check ' +
                    'port and ensure that Brick Daemon is running.')
        else:
            self.do_disconnect()

    def item_double_clicked(self, index):
        position_index = index.sibling(index.row(), 2)

        if position_index.isValid() and position_index.data().startswith(
                'Ext'):
            index = index.parent()

        uid_index = index.sibling(index.row(), 1)

        if uid_index.isValid():
            self.show_plugin(uid_index.data())

    def create_tab_window(self, device_info):
        tab_window = TabWindow(self.tab_widget, device_info.name, self.untab)
        tab_window._info = device_info
        tab_window.set_callback_on_tab(lambda index:
            self.ipcon.get_connection_state() == IPConnection.CONNECTION_STATE_PENDING and \
                self.tab_widget.setTabEnabled(index, False))

        layout = QVBoxLayout(tab_window)
        info_bar = QHBoxLayout()

        # uid
        info_bar.addWidget(QLabel('UID:'))

        label = QLabel('{0}'.format(device_info.uid))
        label.setTextInteractionFlags(Qt.TextSelectableByMouse
                                      | Qt.TextSelectableByKeyboard)

        info_bar.addWidget(label)
        info_bar.addSpacerItem(QSpacerItem(1, 1, QSizePolicy.Expanding))

        # connected uid
        if device_info.connected_uid != '0':
            info_bar.addWidget(QLabel('Connected to:'))

            button = QToolButton()
            button.setText(device_info.connected_uid)
            button.clicked.connect(
                lambda: self.show_plugin(device_info.connected_uid))

            info_bar.addWidget(button)
            info_bar.addSpacerItem(QSpacerItem(1, 1, QSizePolicy.Expanding))

        # position
        info_bar.addWidget(QLabel('Position:'))
        info_bar.addWidget(QLabel('{0}'.format(device_info.position.upper())))

        info_bar.addSpacerItem(QSpacerItem(1, 1, QSizePolicy.Expanding))

        # firmware version
        label_version_name = QLabel('Version:')
        label_version = QLabel('...')

        if not device_info.plugin.has_custom_version(label_version_name,
                                                     label_version):
            label_version_name.setText('FW Version:')
            label_version.setText(
                infos.get_version_string(device_info.plugin.firmware_version))

        info_bar.addWidget(label_version_name)
        info_bar.addWidget(label_version)

        info_bar.addSpacerItem(QSpacerItem(1, 1, QSizePolicy.Expanding))

        # timeouts
        info_bar.addWidget(QLabel('Timeouts:'))
        label_timeouts = QLabel('0')
        info_bar.addWidget(label_timeouts)

        layout.addLayout(info_bar)

        # actions
        actions = device_info.plugin.get_actions()

        if actions != None:
            if type(actions) == QAction:
                button = QPushButton(actions.text())
                button.clicked.connect(actions.trigger)
            else:
                button = QToolButton()
                button.setText(actions[0])
                button.setPopupMode(QToolButton.InstantPopup)
                button.setToolButtonStyle(Qt.ToolButtonTextOnly)
                button.setArrowType(Qt.DownArrow)
                button.setAutoRaise(True)

                menu = QMenu(actions[0])
                button.setMenu(menu)

                for action in actions[1]:
                    menu.addAction(action)

            info_bar.addSpacerItem(QSpacerItem(40, 20, QSizePolicy.Expanding))
            info_bar.addWidget(button)

        line = QFrame()
        line.setFrameShape(QFrame.HLine)
        line.setFrameShadow(QFrame.Sunken)

        device_info.plugin.label_timeouts = label_timeouts
        device_info.plugin.layout().setContentsMargins(0, 0, 0, 0)

        layout.addWidget(line)
        layout.addWidget(device_info.plugin)

        return tab_window

    def tab_move(self, event):
        # visualize rearranging of tabs (if allowed by tab_widget)
        if self.tab_widget.isMovable():
            if event.type(
            ) == QEvent.MouseButtonPress and event.button() & Qt.LeftButton:
                QApplication.setOverrideCursor(QCursor(Qt.SizeHorCursor))

            elif event.type(
            ) == QEvent.MouseButtonRelease and event.button() & Qt.LeftButton:
                QApplication.restoreOverrideCursor()

        return False

    def untab(self, tab_index):
        tab = self.tab_widget.widget(tab_index)
        tab.untab()
        tab._info.plugin.start_plugin()
        self.tab_widget.setCurrentIndex(0)

    def eventFilter(self, source, event):
        if source is self.tab_widget.tabBar():
            return self.tab_move(event)

        return False

    def tab_for_uid(self, uid):
        for i in range(1, self.tab_widget.count()):
            try:
                if self.tab_widget.widget(i)._info.uid == uid:
                    return i
            except:
                pass

        return -1

    def show_plugin(self, uid):
        i = self.tab_for_uid(uid)
        tab_window = infos.get_info(uid).tab_window

        if i > 0 and self.tab_widget.isTabEnabled(i):
            self.tab_widget.setCurrentIndex(i)

        QApplication.setActiveWindow(tab_window)

        tab_window.show()
        tab_window.activateWindow()
        tab_window.raise_()

    def cb_enumerate(self, uid, connected_uid, position, hardware_version,
                     firmware_version, device_identifier, enumeration_type):
        if self.ipcon.get_connection_state(
        ) != IPConnection.CONNECTION_STATE_CONNECTED:
            # ignore enumerate callbacks that arrived after the connection got closed
            return

        if enumeration_type in [
                IPConnection.ENUMERATION_TYPE_AVAILABLE,
                IPConnection.ENUMERATION_TYPE_CONNECTED
        ]:
            device_info = infos.get_info(uid)
            something_changed_ref = [False]

            if device_info == None:
                if device_identifier == BrickMaster.DEVICE_IDENTIFIER:
                    device_info = infos.BrickMasterInfo()
                elif device_identifier == BrickRED.DEVICE_IDENTIFIER:
                    device_info = infos.BrickREDInfo()
                elif position in ('a', 'b', 'c', 'd', 'A', 'B', 'C', 'D'):
                    position = position.lower()
                    device_info = infos.BrickletInfo()
                else:
                    device_info = infos.BrickInfo()
                    something_changed_ref[0] = True

            def set_device_info_value(name, value):
                if getattr(device_info, name) != value:
                    setattr(device_info, name, value)
                    something_changed_ref[0] = True

            set_device_info_value('uid', uid)
            set_device_info_value('connected_uid', connected_uid)
            set_device_info_value('position', position)
            set_device_info_value('hardware_version', hardware_version)
            set_device_info_value('firmware_version_installed',
                                  firmware_version)
            set_device_info_value('device_identifier', device_identifier)
            set_device_info_value('protocol_version', 2)
            set_device_info_value('enumeration_type', enumeration_type)

            if device_info.type == 'bricklet':
                for brick_info in infos.get_brick_infos():
                    if brick_info.uid == device_info.connected_uid:
                        if brick_info.bricklets[position] != device_info:
                            brick_info.bricklets[position] = device_info
                            something_changed_ref[0] = True
            elif device_info.type == 'brick':
                for bricklet_info in infos.get_bricklet_infos():
                    if bricklet_info.connected_uid == device_info.uid:
                        if device_info.bricklets[
                                bricklet_info.position] != bricklet_info:
                            device_info.bricklets[
                                bricklet_info.position] = bricklet_info
                            something_changed_ref[0] = True

            if device_info.plugin == None:
                self.plugin_manager.create_plugin_instance(
                    device_identifier, self.ipcon, device_info)

                device_info.tab_window = self.create_tab_window(device_info)
                device_info.tab_window.setWindowFlags(Qt.Widget)
                device_info.tab_window.tab()

                infos.add_info(device_info)

                something_changed_ref[0] = True

            if something_changed_ref[0]:
                self.update_tree_view()
        elif enumeration_type == IPConnection.ENUMERATION_TYPE_DISCONNECTED:
            for device_info in infos.get_device_infos():
                if device_info.uid == uid:
                    self.tab_widget.setCurrentIndex(0)
                    self.remove_device_info(device_info.uid)

                if device_info.type == 'brick':
                    for port in device_info.bricklets:
                        if device_info.bricklets[
                                port] and device_info.bricklets[
                                    port].uid == uid:
                            device_info.bricklets[port] = None

            self.update_tree_view()

    def hack_to_remove_red_brick_tab(self, red_brick_uid):
        for device_info in infos.get_device_infos():
            if device_info.uid == red_brick_uid:
                self.tab_widget.setCurrentIndex(0)
                self.remove_device_info(device_info.uid)

                self.red_session_losts += 1
                self.label_red_session_losts.setText(
                    'RED Brick Session Loss Count: {0}'.format(
                        self.red_session_losts))
                self.label_red_session_losts.show()

                break

        self.update_tree_view()

    def cb_connected(self, connect_reason):
        self.disconnect_times = []

        self.update_ui_state()

        if connect_reason == IPConnection.CONNECT_REASON_REQUEST:
            self.auto_reconnects = 0
            self.label_auto_reconnects.hide()

            self.red_session_losts = 0
            self.label_red_session_losts.hide()

            self.ipcon.set_auto_reconnect(True)

            index = self.combo_host.findText(self.last_host)

            if index >= 0:
                self.combo_host.removeItem(index)

                host_info = self.host_infos[index]

                del self.host_infos[index]
                self.host_infos.insert(0, host_info)
            else:
                index = self.combo_host.currentIndex()

                host_info = self.host_infos[index].duplicate()
                host_info.host = self.last_host

                self.host_infos.insert(0, host_info)

            self.combo_host.insertItem(-1, self.last_host)
            self.combo_host.setCurrentIndex(0)

            while self.combo_host.count() > config.HOST_INFO_COUNT:
                self.combo_host.removeItem(self.combo_host.count() - 1)

            if not self.do_authenticate(False):
                return

            try:
                self.ipcon.enumerate()
            except:
                self.update_ui_state()
        elif connect_reason == IPConnection.CONNECT_REASON_AUTO_RECONNECT:
            self.auto_reconnects += 1
            self.label_auto_reconnects.setText(
                'Auto-Reconnect Count: {0}'.format(self.auto_reconnects))
            self.label_auto_reconnects.show()

            if not self.do_authenticate(True):
                return

            try:
                self.ipcon.enumerate()
            except:
                self.update_ui_state()
        else:
            try:
                self.ipcon.enumerate()
            except:
                self.update_ui_state()

    def cb_disconnected(self, disconnect_reason):
        if disconnect_reason == IPConnection.DISCONNECT_REASON_REQUEST:
            self.auto_reconnects = 0
            self.label_auto_reconnects.hide()

            self.red_session_losts = 0
            self.label_red_session_losts.hide()

        if disconnect_reason == IPConnection.DISCONNECT_REASON_REQUEST or not self.ipcon.get_auto_reconnect(
        ):
            self.update_ui_state()
        elif len(self.disconnect_times
                 ) >= 3 and self.disconnect_times[-3] < time.time() + 1:
            self.disconnect_times = []
            self.ipcon.set_auto_reconnect(False)
            self.update_ui_state()
            self.reset_view()

            QMessageBox.critical(
                self, 'Connection',
                'Stopped automatic reconnecting due to multiple connection errors in a row.'
            )
        else:
            self.disconnect_times.append(time.time())
            self.update_ui_state(IPConnection.CONNECTION_STATE_PENDING)

    def set_tree_view_defaults(self):
        self.tree_view_model.setHorizontalHeaderLabels(
            self.tree_view_model_labels)
        self.tree_view.expandAll()
        self.tree_view.setColumnWidth(0, 250)
        self.tree_view.setColumnWidth(1, 85)
        self.tree_view.setColumnWidth(2, 85)
        self.tree_view.setColumnWidth(3, 90)
        self.tree_view.setExpandsOnDoubleClick(False)
        self.tree_view.setSortingEnabled(True)
        self.tree_view.header().setSortIndicator(2, Qt.AscendingOrder)

    def update_ui_state(self, connection_state=None):
        # FIXME: need to call processEvents() otherwise get_connection_state()
        #        might return the wrong value
        QApplication.processEvents()

        if connection_state is None:
            connection_state = self.ipcon.get_connection_state()

        self.button_connect.setDisabled(False)
        self.button_flashing.setDisabled(False)

        if connection_state == IPConnection.CONNECTION_STATE_DISCONNECTED:
            self.button_connect.setText('Connect')
            self.combo_host.setDisabled(False)
            self.spinbox_port.setDisabled(False)
            self.checkbox_authentication.setDisabled(False)
            self.edit_secret.setDisabled(False)
            self.button_advanced.setDisabled(True)
        elif connection_state == IPConnection.CONNECTION_STATE_CONNECTED:
            self.button_connect.setText("Disconnect")
            self.combo_host.setDisabled(True)
            self.spinbox_port.setDisabled(True)
            self.checkbox_authentication.setDisabled(True)
            self.edit_secret.setDisabled(True)
            self.update_advanced_window()

            # restart all pause plugins
            for info in infos.get_device_infos():
                info.plugin.resume_plugin()
        elif connection_state == IPConnection.CONNECTION_STATE_PENDING:
            self.button_connect.setText('Abort Pending Automatic Reconnect')
            self.combo_host.setDisabled(True)
            self.spinbox_port.setDisabled(True)
            self.checkbox_authentication.setDisabled(True)
            self.edit_secret.setDisabled(True)
            self.button_advanced.setDisabled(True)
            self.button_flashing.setDisabled(True)

            # pause all running plugins
            for info in infos.get_device_infos():
                info.plugin.pause_plugin()

        enable = connection_state == IPConnection.CONNECTION_STATE_CONNECTED

        for i in range(1, self.tab_widget.count()):
            self.tab_widget.setTabEnabled(i, enable)

        for device_info in infos.get_device_infos():
            device_info.tab_window.setEnabled(enable)

        QApplication.processEvents()

    def update_tree_view(self):
        sis = self.tree_view.header().sortIndicatorSection()
        sio = self.tree_view.header().sortIndicatorOrder()

        self.tree_view_model.clear()

        for info in infos.get_brick_infos():
            parent = [
                QStandardItem(info.name),
                QStandardItem(info.uid),
                QStandardItem(info.position.upper()),
                QStandardItem('.'.join(
                    map(str, info.firmware_version_installed)))
            ]

            for item in parent:
                item.setFlags(item.flags() & ~Qt.ItemIsEditable)

            self.tree_view_model.appendRow(parent)

            for port in sorted(info.bricklets):
                if info.bricklets[port] and info.bricklets[
                        port].protocol_version == 2:
                    child = [
                        QStandardItem(info.bricklets[port].name),
                        QStandardItem(info.bricklets[port].uid),
                        QStandardItem(info.bricklets[port].position.upper()),
                        QStandardItem('.'.join(
                            map(
                                str, info.bricklets[port].
                                firmware_version_installed)))
                    ]
                    for item in child:
                        item.setFlags(item.flags() & ~Qt.ItemIsEditable)
                    parent[0].appendRow(child)

            if info.can_have_extension:
                extensions = []
                if info.extensions['ext0'] != None:
                    extensions.append((info.extensions['ext0'], 'Ext0'))
                if info.extensions['ext1'] != None:
                    extensions.append((info.extensions['ext1'], 'Ext1'))

                for extension in extensions:
                    child = [
                        QStandardItem(extension[0].name),
                        QStandardItem(''),
                        QStandardItem(extension[1]),
                        QStandardItem('')
                    ]
                    for item in child:
                        item.setFlags(item.flags() & ~Qt.ItemIsEditable)
                    parent[0].appendRow(child)

        self.set_tree_view_defaults()
        self.tree_view.header().setSortIndicator(sis, sio)
        self.update_advanced_window()
        self.delayed_refresh_updates_timer.start()

    def update_advanced_window(self):
        self.button_advanced.setEnabled(len(infos.get_brick_infos()) > 0)

    def delayed_refresh_updates(self):
        self.delayed_refresh_updates_timer.stop()

        if self.flashing_window is not None and self.flashing_window.isVisible(
        ):
            self.flashing_window.refresh_updates_clicked()