Exemple #1
0
class E5PassivePopup(QFrame):
    """
    Class implementing dialog-like popup that displays messages without
    interrupting the user.
    """
    Boxed = 0
    Custom = 128
    
    clicked = pyqtSignal((), (QPoint, ))
    
    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent widget (QWidget)
        """
        super(E5PassivePopup, self).__init__(None)
        
        self.__popupStyle = DEFAULT_POPUP_TYPE
        self.__msgView = None
        self.__topLayout = None
        self.__hideDelay = DEFAULT_POPUP_TIME
        self.__hideTimer = QTimer(self)
        self.__autoDelete = False
        self.__fixedPosition = QPoint()
        
        self.setWindowFlags(POPUP_FLAGS)
        self.setFrameStyle(QFrame.Box | QFrame.Plain)
        self.setLineWidth(2)
        self.__hideTimer.timeout.connect(self.hide)
        self.clicked.connect(self.hide)
    
    def setView(self, child):
        """
        Public method to set the message view.
        
        @param child reference to the widget to set as the message view
            (QWidget)
        """
        self.__msgView = child
        self.__topLayout = QVBoxLayout(self)
        self.__topLayout.addWidget(self.__msgView)
        self.__topLayout.activate()
    
    def view(self):
        """
        Public method to get a reference to the message view.
        
        @return reference to the message view (QWidget)
        """
        return self.__msgView
    
    def setVisible(self, visible):
        """
        Public method to show or hide the popup.
        
        @param visible flag indicating the visibility status (boolean)
        """
        if not visible:
            super(E5PassivePopup, self).setVisible(visible)
            return
        
        if self.size() != self.sizeHint():
            self.resize(self.sizeHint())
        
        if self.__fixedPosition.isNull():
            self.__positionSelf()
        else:
            self.move(self.__fixedPosition)
        super(E5PassivePopup, self).setVisible(True)
        
        delay = self.__hideDelay
        if delay < 0:
            delay = DEFAULT_POPUP_TIME
        if delay > 0:
            self.__hideTimer.start(delay)
    
    def show(self, p=None):
        """
        Public slot to show the popup.
        
        @param p position for the popup (QPoint)
        """
        if p is not None:
            self.__fixedPosition = p
        super(E5PassivePopup, self).show()
    
    def setTimeout(self, delay):
        """
        Public method to set the delay for the popup is removed automatically.
        
        Setting the delay to 0 disables the timeout. If you're doing this, you
        may want to connect the clicked() signal to the hide() slot. Setting
        the delay to -1 makes it use the default value.
        
        @param delay value for the delay in milliseconds (integer)
        """
        self.__hideDelay = delay
        if self.__hideTimer.isActive():
            if delay:
                if delay == -1:
                    delay = DEFAULT_POPUP_TIME
                self.__hideTimer.start(delay)
            else:
                self.__hideTimer.stop()
    
    def timeout(self):
        """
        Public method to get the delay before the popup is removed
        automatically.
        
        @return the delay before the popup is removed automatically (integer)
        """
        return self.__hideDelay
    
    def mouseReleaseEvent(self, evt):
        """
        Protected method to handle a mouse release event.
        
        @param evt reference to the mouse event (QMouseEvent)
        """
        self.clicked.emit()
        self.clicked.emit(evt.pos())
    
    def hideEvent(self, evt):
        """
        Protected method to handle the hide event.
        
        @param evt reference to the hide event (QHideEvent)
        """
        self.__hideTimer.stop()
    
    def __defaultArea(self):
        """
        Private method to determine the default rectangle to be passed to
        moveNear().
        
        @return default rectangle (QRect)
        """
        return QRect(100, 100, 200, 200)
    
    def __positionSelf(self):
        """
        Private method to position the popup.
        """
        self.__moveNear(self.__defaultArea())
    
    def __moveNear(self, target):
        """
        Private method to move the popup to be adjacent to the specified
        rectangle.
        
        @param target rectangle to be placed at (QRect)
        """
        pos = self.__calculateNearbyPoint(target)
        self.move(pos.x(), pos.y())
    
    def __calculateNearbyPoint(self, target):
        """
        Private method to calculate the position to place the popup near the
        specified rectangle.
        
        @param target rectangle to be placed at (QRect)
        @return position to place the popup (QPoint)
        """
        pos = target.topLeft()
        x = pos.x()
        y = pos.y()
        w = self.minimumSizeHint().width()
        h = self.minimumSizeHint().height()
        
        r = QApplication.desktop().screenGeometry(
            QPoint(x + w // 2, y + h // 2))
        
        if x < r.center().x():
            x += target.width()
        else:
            x -= w
        
        # It's apparently trying to go off screen, so display it ALL at the
        # bottom.
        if (y + h) > r.bottom():
            y = r.bottom() - h
        
        if (x + w) > r.right():
            x = r.right() - w
        
        if y < r.top():
            y = r.top()
        
        if x < r.left():
            x = r.left()
        
        return QPoint(x, y)
Exemple #2
0
class IMU(PluginBase, Ui_IMU):
    def __init__(self, *args):
        PluginBase.__init__(self, BrickIMU, *args)

        self.setupUi(self)

        self.imu = self.device

        # the firmware version of a Brick can (under common circumstances) not
        # change during the lifetime of a Brick plugin. therefore, it's okay to
        # make final decisions based on it here
        self.has_status_led = self.firmware_version >= (2, 3, 1)

        self.acc_x = CurveValueWrapper()
        self.acc_y = CurveValueWrapper()
        self.acc_z = CurveValueWrapper()
        self.mag_x = CurveValueWrapper()
        self.mag_y = CurveValueWrapper()
        self.mag_z = CurveValueWrapper()
        self.gyr_x = CurveValueWrapper()
        self.gyr_y = CurveValueWrapper()
        self.gyr_z = CurveValueWrapper()
        self.temp  = CurveValueWrapper()
        self.roll  = None
        self.pitch = None
        self.yaw   = None
        self.qua_x = None
        self.qua_y = None
        self.qua_z = None
        self.qua_w = None

        self.all_data_valid = False
        self.quaternion_valid = False
        self.orientation_valid = False

        self.update_timer = QTimer(self)
        self.update_timer.timeout.connect(self.update_data)

        self.cbe_all_data = CallbackEmulator(self.imu.get_all_data,
                                             None,
                                             self.cb_all_data,
                                             self.increase_error_count,
                                             expand_result_tuple_for_callback=True,
                                             use_result_signal=False)
        self.cbe_orientation = CallbackEmulator(self.imu.get_orientation,
                                                None,
                                                self.cb_orientation,
                                                self.increase_error_count,
                                                expand_result_tuple_for_callback=True,
                                                use_result_signal=False)
        self.cbe_quaternion = CallbackEmulator(self.imu.get_quaternion,
                                               None,
                                               self.cb_quaternion,
                                               self.increase_error_count,
                                               expand_result_tuple_for_callback=True,
                                               use_result_signal=False)

        self.imu_gl = IMU3DWidget(self)
        self.imu_gl.setMinimumSize(150, 150)
        self.imu_gl.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        self.update_counter = 0

        self.mag_plot_widget = PlotWidget("Magnetic Field [mG]",
                                          [("X", Qt.red, self.mag_x, str),
                                           ("Y", Qt.darkGreen, self.mag_y, str),
                                           ("Z", Qt.blue, self.mag_z, str)],
                                          clear_button=self.clear_graphs,
                                          key='right-no-icon', y_resolution=5)
        self.acc_plot_widget = PlotWidget("Acceleration [mg]",
                                          [("X", Qt.red, self.acc_x, str),
                                           ("Y", Qt.darkGreen, self.acc_y, str),
                                           ("Z", Qt.blue, self.acc_z, str)],
                                          clear_button=self.clear_graphs,
                                          key='right-no-icon', y_resolution=5)
        self.gyr_plot_widget = PlotWidget("Angular Velocity [°/s]",
                                          [("X", Qt.red, self.gyr_x, str),
                                           ("Y", Qt.darkGreen, self.gyr_y, str),
                                           ("Z", Qt.blue, self.gyr_z, str)],
                                          clear_button=self.clear_graphs,
                                          key='right-no-icon', y_resolution=0.05)
        self.temp_plot_widget = PlotWidget("Temperature [°C]",
                                           [("t", Qt.red, self.temp, str)],
                                           clear_button=self.clear_graphs,
                                           key=None, y_resolution=0.01)

        self.mag_plot_widget.setMinimumSize(250, 250)
        self.acc_plot_widget.setMinimumSize(250, 250)
        self.gyr_plot_widget.setMinimumSize(250, 250)
        self.temp_plot_widget.setMinimumSize(250, 250)

        self.orientation_label = QLabel('Position your IMU Brick as shown in the image above, then press "Save Orientation".')
        self.orientation_label.setWordWrap(True)
        self.orientation_label.setAlignment(Qt.AlignHCenter)
        self.gl_layout = QVBoxLayout()
        self.gl_layout.addWidget(self.imu_gl)
        self.gl_layout.addWidget(self.orientation_label)

        self.layout_top.addWidget(self.gyr_plot_widget)
        self.layout_top.addWidget(self.acc_plot_widget)
        self.layout_top.addWidget(self.mag_plot_widget)
        self.layout_bottom.addLayout(self.gl_layout)
        self.layout_bottom.addWidget(self.temp_plot_widget)

        self.save_orientation.clicked.connect(self.save_orientation_clicked)
        self.calibrate.clicked.connect(self.calibrate_clicked)
        self.led_button.clicked.connect(self.led_clicked)
        self.speed_spinbox.editingFinished.connect(self.speed_finished)

        width = QFontMetrics(self.gyr_x_label.font()).boundingRect('-XXXX.X').width()

        self.gyr_x_label.setMinimumWidth(width)
        self.gyr_y_label.setMinimumWidth(width)
        self.gyr_z_label.setMinimumWidth(width)

        self.calibrate = None
        self.alive = True

        if self.has_status_led:
            self.status_led_action = QAction('Status LED', self)
            self.status_led_action.setCheckable(True)
            self.status_led_action.toggled.connect(lambda checked: self.imu.enable_status_led() if checked else self.imu.disable_status_led())
            self.set_configs([(0, None, [self.status_led_action])])
        else:
            self.status_led_action = None

        reset = QAction('Reset', self)
        reset.triggered.connect(self.imu.reset)
        self.set_actions([(0, None, [reset])])

    def save_orientation_clicked(self):
        self.imu_gl.save_orientation()
        self.orientation_label.hide()

    def cleanup_gl(self):
        self.state = self.imu_gl.get_state()
        self.imu_gl.hide()
        self.imu_gl.cleanup()

    def restart_gl(self):
        self.imu_gl = IMU3DWidget()

        self.imu_gl.setMinimumSize(150, 150)
        self.imu_gl.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.gl_layout.addWidget(self.imu_gl)
        self.imu_gl.show()

        self.save_orientation.clicked.connect(self.save_orientation_clicked)
        self.imu_gl.set_state(self.state)

    def start(self):
        if not self.alive:
            return

        if self.has_status_led:
            async_call(self.imu.is_status_led_enabled, None, self.status_led_action.setChecked, self.increase_error_count)

        self.parent().add_callback_on_untab(lambda x: self.cleanup_gl(), 'imu_cleanup_on_untab')
        self.parent().add_callback_post_untab(lambda x: self.restart_gl(), 'imu_restart_post_untab')
        self.parent().add_callback_on_tab(lambda x: self.cleanup_gl(), 'imu_cleanup_on_tab')
        self.parent().add_callback_post_tab(lambda x: self.restart_gl(), 'imu_restart_post_tab')

        self.gl_layout.activate()
        self.cbe_all_data.set_period(100)
        self.cbe_orientation.set_period(100)
        self.cbe_quaternion.set_period(50)
        self.update_timer.start(50)

        async_call(self.imu.get_convergence_speed, None, self.speed_spinbox.setValue, self.increase_error_count)

        self.mag_plot_widget.stop = False
        self.acc_plot_widget.stop = False
        self.gyr_plot_widget.stop = False
        self.temp_plot_widget.stop = False

    def stop(self):
        self.mag_plot_widget.stop = True
        self.acc_plot_widget.stop = True
        self.gyr_plot_widget.stop = True
        self.temp_plot_widget.stop = True

        self.update_timer.stop()
        self.cbe_all_data.set_period(0)
        self.cbe_orientation.set_period(0)
        self.cbe_quaternion.set_period(0)

    def destroy(self):
        self.alive = False
        self.cleanup_gl()
        if self.calibrate:
            self.calibrate.close()

    @staticmethod
    def has_device_identifier(device_identifier):
        return device_identifier == BrickIMU.DEVICE_IDENTIFIER

    def cb_all_data(self, acc_x, acc_y, acc_z, mag_x, mag_y, mag_z, gyr_x, gyr_y, gyr_z, temp):
        self.acc_x.value = acc_x
        self.acc_y.value = acc_y
        self.acc_z.value = acc_z
        self.mag_x.value = mag_x
        self.mag_y.value = mag_y
        self.mag_z.value = mag_z
        self.gyr_x.value = gyr_x / 14.375
        self.gyr_y.value = gyr_y / 14.375
        self.gyr_z.value = gyr_z / 14.375
        self.temp.value  = temp / 100.0

        self.all_data_valid = True

    def cb_quaternion(self, x, y, z, w):
        self.qua_x = x
        self.qua_y = y
        self.qua_z = z
        self.qua_w = w

        self.quaternion_valid = True

    def cb_orientation(self, roll, pitch, yaw):
        self.roll = roll / 100.0
        self.pitch = pitch / 100.0
        self.yaw = yaw / 100.0

        self.orientation_valid = True

    def led_clicked(self):
        if 'On' in self.led_button.text().replace('&', ''):
            self.led_button.setText('Turn LEDs Off')
            self.imu.leds_on()
        elif 'Off' in self.led_button.text().replace('&', ''):
            self.led_button.setText('Turn LEDs On')
            self.imu.leds_off()

    def update_data(self):
        self.update_counter += 1

        if self.quaternion_valid:
            self.imu_gl.update_orientation(self.qua_w, self.qua_x, self.qua_y, self.qua_z)

        if self.update_counter == 2:
            self.update_counter = 0

            if self.all_data_valid and self.orientation_valid:
                self.acceleration_update(self.acc_x.value, self.acc_y.value, self.acc_z.value)
                self.magnetometer_update(self.mag_x.value, self.mag_y.value, self.mag_z.value)
                self.gyroscope_update(self.gyr_x.value, self.gyr_y.value, self.gyr_z.value)
                self.orientation_update(self.roll, self.pitch, self.yaw)
                self.temperature_update(self.temp.value)

    def acceleration_update(self, x, y, z):
        self.acc_y_label.setText(format(x, '.1f'))
        self.acc_x_label.setText(format(y, '.1f'))
        self.acc_z_label.setText(format(z, '.1f'))

    def magnetometer_update(self, x, y, z):
        # Earth magnetic field: 0.5 Gauss
        self.mag_x_label.setText(format(x, '.1f'))
        self.mag_y_label.setText(format(y, '.1f'))
        self.mag_z_label.setText(format(z, '.1f'))

    def gyroscope_update(self, x, y, z):
        self.gyr_x_label.setText(format(x, '.1f'))
        self.gyr_y_label.setText(format(y, '.1f'))
        self.gyr_z_label.setText(format(z, '.1f'))

    def orientation_update(self, r, p, y):
        self.roll_label.setText(format(r, '.1f'))
        self.pitch_label.setText(format(p, '.1f'))
        self.yaw_label.setText(format(y, '.1f'))

    def temperature_update(self, t):
        self.tem_label.setText(format(t, '.1f'))

    def calibrate_clicked(self):
        self.stop()

        if self.calibrate is None:
            self.calibrate = CalibrateWindow(self)

        self.calibrate.refresh_values()
        self.calibrate.show()

    def speed_finished(self):
        speed = self.speed_spinbox.value()
        self.imu.set_convergence_speed(speed)
class E5PassivePopup(QFrame):
    """
    Class implementing dialog-like popup that displays messages without
    interrupting the user.
    
    @signal clicked emitted to indicate a mouse button click
    """
    Boxed = 0
    Custom = 128

    clicked = pyqtSignal((), (QPoint, ))

    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent widget (QWidget)
        """
        super(E5PassivePopup, self).__init__(None)

        self.__popupStyle = DEFAULT_POPUP_TYPE
        self.__msgView = None
        self.__topLayout = None
        self.__hideDelay = DEFAULT_POPUP_TIME
        self.__hideTimer = QTimer(self)
        self.__autoDelete = False
        self.__fixedPosition = QPoint()

        self.setWindowFlags(POPUP_FLAGS)
        self.setFrameStyle(QFrame.Box | QFrame.Plain)
        self.setLineWidth(2)
        self.__hideTimer.timeout.connect(self.hide)
        self.clicked.connect(self.hide)

        self.__customData = {}  # dictionary to store some custom data

    def setView(self, child):
        """
        Public method to set the message view.
        
        @param child reference to the widget to set as the message view
            (QWidget)
        """
        self.__msgView = child
        self.__topLayout = QVBoxLayout(self)
        self.__topLayout.addWidget(self.__msgView)
        self.__topLayout.activate()

    def view(self):
        """
        Public method to get a reference to the message view.
        
        @return reference to the message view (QWidget)
        """
        return self.__msgView

    def setVisible(self, visible):
        """
        Public method to show or hide the popup.
        
        @param visible flag indicating the visibility status (boolean)
        """
        if not visible:
            super(E5PassivePopup, self).setVisible(visible)
            return

        if self.size() != self.sizeHint():
            self.resize(self.sizeHint())

        if self.__fixedPosition.isNull():
            self.__positionSelf()
        else:
            self.move(self.__fixedPosition)
        super(E5PassivePopup, self).setVisible(True)

        delay = self.__hideDelay
        if delay < 0:
            delay = DEFAULT_POPUP_TIME
        if delay > 0:
            self.__hideTimer.start(delay)

    def show(self, p=None):
        """
        Public slot to show the popup.
        
        @param p position for the popup (QPoint)
        """
        if p is not None:
            self.__fixedPosition = p
        super(E5PassivePopup, self).show()

    def setTimeout(self, delay):
        """
        Public method to set the delay for the popup is removed automatically.
        
        Setting the delay to 0 disables the timeout. If you're doing this, you
        may want to connect the clicked() signal to the hide() slot. Setting
        the delay to -1 makes it use the default value.
        
        @param delay value for the delay in milliseconds (integer)
        """
        self.__hideDelay = delay
        if self.__hideTimer.isActive():
            if delay:
                if delay == -1:
                    delay = DEFAULT_POPUP_TIME
                self.__hideTimer.start(delay)
            else:
                self.__hideTimer.stop()

    def timeout(self):
        """
        Public method to get the delay before the popup is removed
        automatically.
        
        @return the delay before the popup is removed automatically (integer)
        """
        return self.__hideDelay

    def mouseReleaseEvent(self, evt):
        """
        Protected method to handle a mouse release event.
        
        @param evt reference to the mouse event (QMouseEvent)
        """
        self.clicked.emit()
        self.clicked.emit(evt.pos())

    def hideEvent(self, evt):
        """
        Protected method to handle the hide event.
        
        @param evt reference to the hide event (QHideEvent)
        """
        self.__hideTimer.stop()

    def __defaultArea(self):
        """
        Private method to determine the default rectangle to be passed to
        moveNear().
        
        @return default rectangle (QRect)
        """
        return QRect(100, 100, 200, 200)

    def __positionSelf(self):
        """
        Private method to position the popup.
        """
        self.__moveNear(self.__defaultArea())

    def __moveNear(self, target):
        """
        Private method to move the popup to be adjacent to the specified
        rectangle.
        
        @param target rectangle to be placed at (QRect)
        """
        pos = self.__calculateNearbyPoint(target)
        self.move(pos.x(), pos.y())

    def __calculateNearbyPoint(self, target):
        """
        Private method to calculate the position to place the popup near the
        specified rectangle.
        
        @param target rectangle to be placed at (QRect)
        @return position to place the popup (QPoint)
        """
        pos = target.topLeft()
        x = pos.x()
        y = pos.y()
        w = self.minimumSizeHint().width()
        h = self.minimumSizeHint().height()

        if qVersionTuple() >= (5, 10, 0):
            r = (QApplication.screenAt(QPoint(x + w // 2,
                                              y + h // 2)).geometry())
        else:
            r = QApplication.desktop().screenGeometry(
                QPoint(x + w // 2, y + h // 2))

        if x < r.center().x():
            x += target.width()
        else:
            x -= w

        # It's apparently trying to go off screen, so display it ALL at the
        # bottom.
        if (y + h) > r.bottom():
            y = r.bottom() - h

        if (x + w) > r.right():
            x = r.right() - w

        if y < r.top():
            y = r.top()

        if x < r.left():
            x = r.left()

        return QPoint(x, y)

    def setCustomData(self, key, data):
        """
        Public method to set some custom data.
        
        @param key key for the custom data
        @type str
        @param data data to be stored
        @type any
        """
        self.__customData[key] = data

    def getCustomData(self, key):
        """
        Public method to get some custom data.
        
        @param key key for the custom data
        @type str
        @return stored data
        @rtype any
        """
        return self.__customData[key]
Exemple #4
0
class IMU(PluginBase, Ui_IMU):
    def __init__(self, *args):
        PluginBase.__init__(self, BrickIMU, *args)

        self.setupUi(self)

        self.imu = self.device

        # the firmware version of a Brick can (under common circumstances) not
        # change during the lifetime of a Brick plugin. therefore, it's okay to
        # make final decisions based on it here
        self.has_status_led = self.firmware_version >= (2, 3, 1)

        self.acc_x = CurveValueWrapper()
        self.acc_y = CurveValueWrapper()
        self.acc_z = CurveValueWrapper()
        self.mag_x = CurveValueWrapper()
        self.mag_y = CurveValueWrapper()
        self.mag_z = CurveValueWrapper()
        self.gyr_x = CurveValueWrapper()
        self.gyr_y = CurveValueWrapper()
        self.gyr_z = CurveValueWrapper()
        self.temp = CurveValueWrapper()
        self.roll = None
        self.pitch = None
        self.yaw = None
        self.qua_x = None
        self.qua_y = None
        self.qua_z = None
        self.qua_w = None

        self.all_data_valid = False
        self.quaternion_valid = False
        self.orientation_valid = False

        self.update_timer = QTimer(self)
        self.update_timer.timeout.connect(self.update_data)

        self.cbe_all_data = CallbackEmulator(
            self.imu.get_all_data,
            None,
            self.cb_all_data,
            self.increase_error_count,
            expand_result_tuple_for_callback=True,
            use_result_signal=False)
        self.cbe_orientation = CallbackEmulator(
            self.imu.get_orientation,
            None,
            self.cb_orientation,
            self.increase_error_count,
            expand_result_tuple_for_callback=True,
            use_result_signal=False)
        self.cbe_quaternion = CallbackEmulator(
            self.imu.get_quaternion,
            None,
            self.cb_quaternion,
            self.increase_error_count,
            expand_result_tuple_for_callback=True,
            use_result_signal=False)

        self.imu_gl = IMU3DWidget(self)
        self.imu_gl.setMinimumSize(150, 150)
        self.imu_gl.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        self.update_counter = 0

        self.mag_plot_widget = PlotWidget(
            "Magnetic Field [µT]", [("X", Qt.red, self.mag_x, str),
                                    ("Y", Qt.darkGreen, self.mag_y, str),
                                    ("Z", Qt.blue, self.mag_z, str)],
            clear_button=self.clear_graphs,
            key='right-no-icon',
            y_resolution=5)
        self.acc_plot_widget = PlotWidget(
            "Acceleration [mg]", [("X", Qt.red, self.acc_x, str),
                                  ("Y", Qt.darkGreen, self.acc_y, str),
                                  ("Z", Qt.blue, self.acc_z, str)],
            clear_button=self.clear_graphs,
            key='right-no-icon',
            y_resolution=5)
        self.gyr_plot_widget = PlotWidget(
            "Angular Velocity [°/s]", [("X", Qt.red, self.gyr_x, str),
                                       ("Y", Qt.darkGreen, self.gyr_y, str),
                                       ("Z", Qt.blue, self.gyr_z, str)],
            clear_button=self.clear_graphs,
            key='right-no-icon',
            y_resolution=0.05)
        self.temp_plot_widget = PlotWidget("Temperature [°C]",
                                           [("t", Qt.red, self.temp, str)],
                                           clear_button=self.clear_graphs,
                                           key=None,
                                           y_resolution=0.01)

        self.mag_plot_widget.setMinimumSize(250, 250)
        self.acc_plot_widget.setMinimumSize(250, 250)
        self.gyr_plot_widget.setMinimumSize(250, 250)
        self.temp_plot_widget.setMinimumSize(250, 250)

        self.orientation_label = QLabel(
            'Position your IMU Brick as shown in the image above, then press "Save Orientation".'
        )
        self.orientation_label.setWordWrap(True)
        self.orientation_label.setAlignment(Qt.AlignHCenter)
        self.gl_layout = QVBoxLayout()
        self.gl_layout.addWidget(self.imu_gl)
        self.gl_layout.addWidget(self.orientation_label)

        self.layout_top.addWidget(self.gyr_plot_widget)
        self.layout_top.addWidget(self.acc_plot_widget)
        self.layout_top.addWidget(self.mag_plot_widget)
        self.layout_bottom.addLayout(self.gl_layout)
        self.layout_bottom.addWidget(self.temp_plot_widget)

        self.save_orientation.clicked.connect(self.save_orientation_clicked)
        self.calibrate.clicked.connect(self.calibrate_clicked)
        self.led_button.clicked.connect(self.led_clicked)
        self.speed_spinbox.editingFinished.connect(self.speed_finished)

        width = QFontMetrics(
            self.gyr_x_label.font()).boundingRect('-XXXX.X').width()

        self.gyr_x_label.setMinimumWidth(width)
        self.gyr_y_label.setMinimumWidth(width)
        self.gyr_z_label.setMinimumWidth(width)

        self.calibrate = None
        self.alive = True

        if self.has_status_led:
            self.status_led_action = QAction('Status LED', self)
            self.status_led_action.setCheckable(True)
            self.status_led_action.toggled.connect(
                lambda checked: self.imu.enable_status_led()
                if checked else self.imu.disable_status_led())
            self.set_configs([(0, None, [self.status_led_action])])
        else:
            self.status_led_action = None

        reset = QAction('Reset', self)
        reset.triggered.connect(self.imu.reset)
        self.set_actions([(0, None, [reset])])

    def save_orientation_clicked(self):
        self.imu_gl.save_orientation()
        self.orientation_label.hide()

    def cleanup_gl(self):
        self.state = self.imu_gl.get_state()
        self.imu_gl.hide()
        self.imu_gl.cleanup()

    def restart_gl(self):
        self.imu_gl = IMU3DWidget()

        self.imu_gl.setMinimumSize(150, 150)
        self.imu_gl.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.gl_layout.addWidget(self.imu_gl)
        self.imu_gl.show()

        self.save_orientation.clicked.connect(self.save_orientation_clicked)
        self.imu_gl.set_state(self.state)

    def start(self):
        if not self.alive:
            return

        if self.has_status_led:
            async_call(self.imu.is_status_led_enabled, None,
                       self.status_led_action.setChecked,
                       self.increase_error_count)

        self.parent().add_callback_on_untab(lambda x: self.cleanup_gl(),
                                            'imu_cleanup_on_untab')
        self.parent().add_callback_post_untab(lambda x: self.restart_gl(),
                                              'imu_restart_post_untab')
        self.parent().add_callback_on_tab(lambda x: self.cleanup_gl(),
                                          'imu_cleanup_on_tab')
        self.parent().add_callback_post_tab(lambda x: self.restart_gl(),
                                            'imu_restart_post_tab')

        self.gl_layout.activate()
        self.cbe_all_data.set_period(100)
        self.cbe_orientation.set_period(100)
        self.cbe_quaternion.set_period(50)
        self.update_timer.start(50)

        async_call(self.imu.get_convergence_speed, None,
                   self.speed_spinbox.setValue, self.increase_error_count)

        self.mag_plot_widget.stop = False
        self.acc_plot_widget.stop = False
        self.gyr_plot_widget.stop = False
        self.temp_plot_widget.stop = False

    def stop(self):
        self.mag_plot_widget.stop = True
        self.acc_plot_widget.stop = True
        self.gyr_plot_widget.stop = True
        self.temp_plot_widget.stop = True

        self.update_timer.stop()
        self.cbe_all_data.set_period(0)
        self.cbe_orientation.set_period(0)
        self.cbe_quaternion.set_period(0)

    def destroy(self):
        self.alive = False
        self.cleanup_gl()
        if self.calibrate:
            self.calibrate.close()

    @staticmethod
    def has_device_identifier(device_identifier):
        return device_identifier == BrickIMU.DEVICE_IDENTIFIER

    def cb_all_data(self, acc_x, acc_y, acc_z, mag_x, mag_y, mag_z, gyr_x,
                    gyr_y, gyr_z, temp):
        self.acc_x.value = acc_x
        self.acc_y.value = acc_y
        self.acc_z.value = acc_z
        self.mag_x.value = mag_x / 10
        self.mag_y.value = mag_y / 10
        self.mag_z.value = mag_z / 10
        self.gyr_x.value = gyr_x / 14.375
        self.gyr_y.value = gyr_y / 14.375
        self.gyr_z.value = gyr_z / 14.375
        self.temp.value = temp / 100.0

        self.all_data_valid = True

    def cb_quaternion(self, x, y, z, w):
        self.qua_x = x
        self.qua_y = y
        self.qua_z = z
        self.qua_w = w

        self.quaternion_valid = True

    def cb_orientation(self, roll, pitch, yaw):
        self.roll = roll / 100.0
        self.pitch = pitch / 100.0
        self.yaw = yaw / 100.0

        self.orientation_valid = True

    def led_clicked(self):
        if 'On' in self.led_button.text().replace('&', ''):
            self.led_button.setText('Turn LEDs Off')
            self.imu.leds_on()
        elif 'Off' in self.led_button.text().replace('&', ''):
            self.led_button.setText('Turn LEDs On')
            self.imu.leds_off()

    def update_data(self):
        self.update_counter += 1

        if self.quaternion_valid:
            self.imu_gl.update_orientation(self.qua_w, self.qua_x, self.qua_y,
                                           self.qua_z)

        if self.update_counter == 2:
            self.update_counter = 0

            if self.all_data_valid and self.orientation_valid:
                self.acceleration_update(self.acc_x.value, self.acc_y.value,
                                         self.acc_z.value)
                self.magnetometer_update(self.mag_x.value, self.mag_y.value,
                                         self.mag_z.value)
                self.gyroscope_update(self.gyr_x.value, self.gyr_y.value,
                                      self.gyr_z.value)
                self.orientation_update(self.roll, self.pitch, self.yaw)
                self.temperature_update(self.temp.value)

    def acceleration_update(self, x, y, z):
        self.acc_y_label.setText(format(x, '.1f'))
        self.acc_x_label.setText(format(y, '.1f'))
        self.acc_z_label.setText(format(z, '.1f'))

    def magnetometer_update(self, x, y, z):
        # Earth magnetic field: 0.5 Gauss
        self.mag_x_label.setText(format(x, '.1f'))
        self.mag_y_label.setText(format(y, '.1f'))
        self.mag_z_label.setText(format(z, '.1f'))

    def gyroscope_update(self, x, y, z):
        self.gyr_x_label.setText(format(x, '.1f'))
        self.gyr_y_label.setText(format(y, '.1f'))
        self.gyr_z_label.setText(format(z, '.1f'))

    def orientation_update(self, r, p, y):
        self.roll_label.setText(format(r, '.1f'))
        self.pitch_label.setText(format(p, '.1f'))
        self.yaw_label.setText(format(y, '.1f'))

    def temperature_update(self, t):
        self.tem_label.setText(format(t, '.1f'))

    def calibrate_clicked(self):
        self.stop()

        if self.calibrate is None:
            self.calibrate = CalibrateWindow(self)

        self.calibrate.refresh_values()
        self.calibrate.show()

    def speed_finished(self):
        speed = self.speed_spinbox.value()
        self.imu.set_convergence_speed(speed)
Exemple #5
0
class IMUV3(COMCUPluginBase, Ui_IMUV3):
    def __init__(self, *args):
        COMCUPluginBase.__init__(self, BrickletIMUV3, *args)

        self.setupUi(self)

        self.imu = self.device

        self.cbe_all_data = CallbackEmulator(self, self.imu.get_all_data, None,
                                             self.cb_all_data,
                                             self.increase_error_count)

        self.imu_gl = IMUV33DWidget(self)
        self.imu_gl.setFixedSize(200, 200)

        self.imu_gl_wrapper = None
        self.state = None

        self.data_plot_widget = []

        self.sensor_data = [CurveValueWrapper() for i in range(23)]

        self.data_labels = [
            self.label_acceleration_x, self.label_acceleration_y,
            self.label_acceleration_z, self.label_magnetic_field_x,
            self.label_magnetic_field_y, self.label_magnetic_field_z,
            self.label_angular_velocity_x, self.label_angular_velocity_y,
            self.label_angular_velocity_z, self.label_euler_angle_heading,
            self.label_euler_angle_roll, self.label_euler_angle_pitch,
            self.label_quaternion_w, self.label_quaternion_x,
            self.label_quaternion_y, self.label_quaternion_z,
            self.label_linear_acceleration_x, self.label_linear_acceleration_y,
            self.label_linear_acceleration_z, self.label_gravity_vector_x,
            self.label_gravity_vector_y, self.label_gravity_vector_z,
            self.label_temperature
        ]

        self.data_rows = [
            [
                self.label_acceleration_11, self.label_acceleration_21,
                self.label_acceleration_22, self.label_acceleration_23,
                self.label_acceleration_41, self.label_acceleration_42,
                self.label_acceleration_43, self.label_acceleration_x,
                self.label_acceleration_y, self.label_acceleration_z
            ],
            [
                self.label_magnetic_field_11, self.label_magnetic_field_21,
                self.label_magnetic_field_22, self.label_magnetic_field_23,
                self.label_magnetic_field_41, self.label_magnetic_field_42,
                self.label_magnetic_field_43, self.label_magnetic_field_x,
                self.label_magnetic_field_y, self.label_magnetic_field_z
            ],
            [
                self.label_angular_velocity_11, self.label_angular_velocity_21,
                self.label_angular_velocity_22, self.label_angular_velocity_23,
                self.label_angular_velocity_41, self.label_angular_velocity_42,
                self.label_angular_velocity_43, self.label_angular_velocity_x,
                self.label_angular_velocity_y, self.label_angular_velocity_z
            ],
            [
                self.label_euler_angle_11, self.label_euler_angle_21,
                self.label_euler_angle_22, self.label_euler_angle_23,
                self.label_euler_angle_41, self.label_euler_angle_42,
                self.label_euler_angle_43, self.label_euler_angle_roll,
                self.label_euler_angle_pitch, self.label_euler_angle_heading
            ],
            [
                self.label_quaternion_11, self.label_quaternion_21,
                self.label_quaternion_22, self.label_quaternion_23,
                self.label_quaternion_24, self.label_quaternion_41,
                self.label_quaternion_42, self.label_quaternion_43,
                self.label_quaternion_44, self.label_quaternion_w,
                self.label_quaternion_x, self.label_quaternion_y,
                self.label_quaternion_z
            ],
            [
                self.label_linear_acceleration_11,
                self.label_linear_acceleration_21,
                self.label_linear_acceleration_22,
                self.label_linear_acceleration_23,
                self.label_linear_acceleration_41,
                self.label_linear_acceleration_42,
                self.label_linear_acceleration_43,
                self.label_linear_acceleration_x,
                self.label_linear_acceleration_y,
                self.label_linear_acceleration_z
            ],
            [
                self.label_gravity_vector_11, self.label_gravity_vector_21,
                self.label_gravity_vector_22, self.label_gravity_vector_23,
                self.label_gravity_vector_41, self.label_gravity_vector_42,
                self.label_gravity_vector_43, self.label_gravity_vector_x,
                self.label_gravity_vector_y, self.label_gravity_vector_z
            ],
            [
                self.label_temperature_11, self.label_temperature_21,
                self.label_temperature_41, self.label_temperature
            ]
        ]

        even_color = QColor(240, 240, 240)
        odd_color = QColor(255, 255, 255)

        self.data_color = [(Qt.red, even_color), (Qt.darkGreen, even_color),
                           (Qt.blue, even_color), (Qt.red, odd_color),
                           (Qt.darkGreen, odd_color), (Qt.blue, odd_color),
                           (Qt.red, even_color), (Qt.darkGreen, even_color),
                           (Qt.blue, even_color), (Qt.red, odd_color),
                           (Qt.darkGreen, odd_color), (Qt.blue, odd_color),
                           (Qt.magenta, even_color), (Qt.red, even_color),
                           (Qt.darkGreen, even_color), (Qt.blue, even_color),
                           (Qt.red, odd_color), (Qt.darkGreen, odd_color),
                           (Qt.blue, odd_color), (Qt.red, even_color),
                           (Qt.darkGreen, even_color), (Qt.blue, even_color),
                           (Qt.magenta, odd_color)]

        even_palette = QPalette()
        even_palette.setColor(QPalette.Window, even_color)
        odd_palette = QPalette()
        odd_palette.setColor(QPalette.Window, odd_color)

        for i, row in enumerate(self.data_rows):
            for label in row:
                if i % 2:
                    label.setPalette(odd_palette)
                else:
                    label.setPalette(even_palette)

                label.setAutoFillBackground(True)

        self.plot_timer = QTimer(self)
        self.plot_timer.start(100)

        for i in range(23):
            self.data_plot_widget.append(
                PlotWidget(
                    "",
                    [("", self.data_color[i][0], self.sensor_data[i], str)],
                    clear_button=self.clear_graphs,
                    x_scale_visible=False,
                    y_scale_visible=False,
                    curve_outer_border_visible=False,
                    curve_motion='smooth',
                    canvas_color=self.data_color[i][1],
                    external_timer=self.plot_timer,
                    curve_start='right',
                    key=None,
                    y_resolution=0.01))

        for w in self.data_plot_widget:
            w.setMinimumHeight(15)
            w.setMaximumHeight(25)

        for i in range(23):
            self.data_grid.addWidget(self.data_plot_widget[i], i, 4)

        self.data_grid.setColumnMinimumWidth(2, 75)

        self.gl_layout = QVBoxLayout()
        self.gl_layout.addWidget(self.imu_gl)
        self.layout_bottom.addLayout(self.gl_layout)
        self.save_orientation.clicked.connect(self.save_orientation_clicked)
        self.button_detach_3d_view.clicked.connect(self.detach_3d_view_clicked)

        self.button_calibration.clicked.connect(self.calibration_clicked)
        self.calibration_color = [
            Qt.red, QColor(0xFF, 0xA0, 0x00), Qt.yellow, Qt.darkGreen
        ]

        self.calibration = None
        self.alive = True
        self.callback_counter = 0

    def save_orientation_clicked(self):
        self.imu_gl.save_orientation()
        if self.imu_gl_wrapper is not None:
            self.imu_gl_wrapper.glWidget.save_orientation()
        self.orientation_label.hide()

    def cleanup_gl(self):
        self.state = self.imu_gl.get_state()
        self.imu_gl.hide()
        self.imu_gl.cleanup()

    def restart_gl(self):
        self.imu_gl = IMUV33DWidget()

        self.imu_gl.setFixedSize(200, 200)
        self.gl_layout.addWidget(self.imu_gl)
        self.imu_gl.show()

        self.save_orientation.clicked.connect(self.save_orientation_clicked)
        self.imu_gl.set_state(self.state)

    def start(self):
        if not self.alive:
            return

        self.parent().add_callback_pre_tab(
            lambda tab_window: self.cleanup_gl(), 'imu_v3_cleanup_pre_tab')
        self.parent().add_callback_post_tab(
            lambda tab_window, tab_index: self.restart_gl(),
            'imu_v3_restart_post_tab')
        self.parent().add_callback_pre_untab(
            lambda tab_window, tab_index: self.cleanup_gl(),
            'imu_v3_cleanup_pre_untab')
        self.parent().add_callback_post_untab(
            lambda tab_window: self.restart_gl(), 'imu_v3_restart_post_untab')

        self.gl_layout.activate()

        self.cbe_all_data.set_period(50)

        for w in self.data_plot_widget:
            w.stop = False

    def stop(self):
        for w in self.data_plot_widget:
            w.stop = True

        if self.imu_gl_wrapper == None:
            self.cbe_all_data.set_period(0)

    def destroy(self):
        self.alive = False

        self.cleanup_gl()
        # Stop callback to fix deadlock with callback emulation thread.
        self.cbe_all_data.set_period(0)

        if self.calibration != None:
            self.calibration.close()

        if self.imu_gl_wrapper != None:
            self.imu_gl_wrapper.close()

    @staticmethod
    def has_device_identifier(device_identifier):
        return device_identifier == BrickletIMUV3.DEVICE_IDENTIFIER

    def calibration_clicked(self):
        if self.calibration is None:
            self.calibration = Calibration(self)

        self.button_calibration.setEnabled(False)
        self.calibration.show()

    def detach_3d_view_clicked(self):
        if self.imu_gl_wrapper != None:
            self.imu_gl_wrapper.close()

        self.button_detach_3d_view.setEnabled(False)

        self.imu_gl_wrapper = WrapperWidget(self)
        self.imu_gl_wrapper.glWidget.set_state(self.imu_gl.get_state())
        self.save_orientation.clicked.connect(self.save_orientation_clicked)

        self.imu_gl_wrapper.show()

    def cb_all_data(self, data):
        self.callback_counter += 1

        if self.callback_counter == 2:
            self.callback_counter = 0

            self.sensor_data[0].value = data.acceleration[0] / 100.0
            self.sensor_data[1].value = data.acceleration[1] / 100.0
            self.sensor_data[2].value = data.acceleration[2] / 100.0
            self.sensor_data[3].value = data.magnetic_field[0] / 16.0
            self.sensor_data[4].value = data.magnetic_field[1] / 16.0
            self.sensor_data[5].value = data.magnetic_field[2] / 16.0
            self.sensor_data[6].value = data.angular_velocity[0] / 16.0
            self.sensor_data[7].value = data.angular_velocity[1] / 16.0
            self.sensor_data[8].value = data.angular_velocity[2] / 16.0
            self.sensor_data[9].value = data.euler_angle[0] / 16.0
            self.sensor_data[10].value = data.euler_angle[1] / 16.0
            self.sensor_data[11].value = data.euler_angle[2] / 16.0
            self.sensor_data[12].value = data.quaternion[0] / (2**14 - 1)
            self.sensor_data[13].value = data.quaternion[1] / (2**14 - 1)
            self.sensor_data[14].value = data.quaternion[2] / (2**14 - 1)
            self.sensor_data[15].value = data.quaternion[3] / (2**14 - 1)
            self.sensor_data[16].value = data.linear_acceleration[0] / 100.0
            self.sensor_data[17].value = data.linear_acceleration[1] / 100.0
            self.sensor_data[18].value = data.linear_acceleration[2] / 100.0
            self.sensor_data[19].value = data.gravity_vector[0] / 100.0
            self.sensor_data[20].value = data.gravity_vector[1] / 100.0
            self.sensor_data[21].value = data.gravity_vector[2] / 100.0
            self.sensor_data[22].value = data.temperature

            for i in range(23):
                self.data_labels[i].setText("{0:.2f}".format(
                    self.sensor_data[i].value))

            self.imu_gl.update_orientation(self.sensor_data[12].value,
                                           self.sensor_data[13].value,
                                           self.sensor_data[14].value,
                                           self.sensor_data[15].value)

            if self.imu_gl_wrapper is not None:
                self.imu_gl_wrapper.glWidget.update_orientation(
                    self.sensor_data[12].value, self.sensor_data[13].value,
                    self.sensor_data[14].value, self.sensor_data[15].value)

            cal_mag = data.calibration_status & 3
            cal_acc = (data.calibration_status & (3 << 2)) >> 2
            cal_gyr = (data.calibration_status & (3 << 4)) >> 4
            cal_sys = (data.calibration_status & (3 << 6)) >> 6

            if self.calibration != None:
                self.calibration.save_calibration.setEnabled(
                    data.calibration_status == 0xFF)

                self.calibration.mag_color.set_color(
                    self.calibration_color[cal_mag])
                self.calibration.acc_color.set_color(
                    self.calibration_color[cal_acc])
                self.calibration.gyr_color.set_color(
                    self.calibration_color[cal_gyr])
                self.calibration.sys_color.set_color(
                    self.calibration_color[cal_sys])
        else:
            self.imu_gl.update_orientation(data.quaternion[0] / (2**14 - 1),
                                           data.quaternion[1] / (2**14 - 1),
                                           data.quaternion[2] / (2**14 - 1),
                                           data.quaternion[3] / (2**14 - 1))

            if self.imu_gl_wrapper is not None:
                self.imu_gl_wrapper.glWidget.update_orientation(
                    data.quaternion[0] / (2**14 - 1),
                    data.quaternion[1] / (2**14 - 1),
                    data.quaternion[2] / (2**14 - 1),
                    data.quaternion[3] / (2**14 - 1))
Exemple #6
0
class IMUV2(PluginBase, Ui_IMUV2):
    def __init__(self, *args):
        PluginBase.__init__(self, BrickIMUV2, *args)

        self.setupUi(self)

        self.imu = self.device

        self.cbe_all_data = CallbackEmulator(self.imu.get_all_data,
                                             None,
                                             self.cb_all_data,
                                             self.increase_error_count)

        self.imu_gl = IMUV23DWidget(self)
        self.imu_gl.setFixedSize(200, 200)

        self.imu_gl_wrapper = None

        self.data_plot_widget = []

        self.sensor_data = [CurveValueWrapper() for i in range(23)]

        self.data_labels = [self.label_acceleration_x, self.label_acceleration_y, self.label_acceleration_z,
                            self.label_magnetic_field_x, self.label_magnetic_field_y, self.label_magnetic_field_z,
                            self.label_angular_velocity_x, self.label_angular_velocity_y, self.label_angular_velocity_z,
                            self.label_euler_angle_heading, self.label_euler_angle_roll, self.label_euler_angle_pitch,
                            self.label_quaternion_w, self.label_quaternion_x, self.label_quaternion_y, self.label_quaternion_z,
                            self.label_linear_acceleration_x, self.label_linear_acceleration_y, self.label_linear_acceleration_z,
                            self.label_gravity_vector_x, self.label_gravity_vector_y, self.label_gravity_vector_z,
                            self.label_temperature]

        self.data_rows = [[self.label_acceleration_11, self.label_acceleration_21, self.label_acceleration_22, self.label_acceleration_23, self.label_acceleration_41, self.label_acceleration_42, self.label_acceleration_43, self.label_acceleration_x, self.label_acceleration_y, self.label_acceleration_z],
                          [self.label_magnetic_field_11, self.label_magnetic_field_21, self.label_magnetic_field_22, self.label_magnetic_field_23, self.label_magnetic_field_41, self.label_magnetic_field_42, self.label_magnetic_field_43, self.label_magnetic_field_x, self.label_magnetic_field_y, self.label_magnetic_field_z],
                          [self.label_angular_velocity_11, self.label_angular_velocity_21, self.label_angular_velocity_22, self.label_angular_velocity_23, self.label_angular_velocity_41, self.label_angular_velocity_42, self.label_angular_velocity_43, self.label_angular_velocity_x, self.label_angular_velocity_y, self.label_angular_velocity_z],
                          [self.label_euler_angle_11, self.label_euler_angle_21, self.label_euler_angle_22, self.label_euler_angle_23, self.label_euler_angle_41, self.label_euler_angle_42, self.label_euler_angle_43, self.label_euler_angle_roll, self.label_euler_angle_pitch, self.label_euler_angle_heading],
                          [self.label_quaternion_11, self.label_quaternion_21, self.label_quaternion_22, self.label_quaternion_23, self.label_quaternion_24, self.label_quaternion_41, self.label_quaternion_42, self.label_quaternion_43, self.label_quaternion_44, self.label_quaternion_w, self.label_quaternion_x, self.label_quaternion_y, self.label_quaternion_z],
                          [self.label_linear_acceleration_11, self.label_linear_acceleration_21, self.label_linear_acceleration_22, self.label_linear_acceleration_23, self.label_linear_acceleration_41, self.label_linear_acceleration_42, self.label_linear_acceleration_43, self.label_linear_acceleration_x, self.label_linear_acceleration_y, self.label_linear_acceleration_z],
                          [self.label_gravity_vector_11, self.label_gravity_vector_21, self.label_gravity_vector_22, self.label_gravity_vector_23, self.label_gravity_vector_41, self.label_gravity_vector_42, self.label_gravity_vector_43, self.label_gravity_vector_x, self.label_gravity_vector_y, self.label_gravity_vector_z],
                          [self.label_temperature_11, self.label_temperature_21, self.label_temperature_41, self.label_temperature]]

        even_color = QColor(240, 240, 240)
        odd_color = QColor(255, 255, 255)

        self.data_color = [(Qt.red, even_color), (Qt.darkGreen, even_color), (Qt.blue, even_color),
                           (Qt.red, odd_color), (Qt.darkGreen, odd_color), (Qt.blue, odd_color),
                           (Qt.red, even_color), (Qt.darkGreen, even_color), (Qt.blue, even_color),
                           (Qt.red, odd_color), (Qt.darkGreen, odd_color), (Qt.blue, odd_color),
                           (Qt.magenta, even_color), (Qt.red, even_color), (Qt.darkGreen, even_color), (Qt.blue, even_color),
                           (Qt.red, odd_color), (Qt.darkGreen, odd_color), (Qt.blue, odd_color),
                           (Qt.red, even_color), (Qt.darkGreen, even_color), (Qt.blue, even_color),
                           (Qt.magenta, odd_color)]


        even_palette = QPalette()
        even_palette.setColor(QPalette.Window, even_color)
        odd_palette = QPalette()
        odd_palette.setColor(QPalette.Window, odd_color)

        for i, row in enumerate(self.data_rows):
            for label in row:
                if i % 2:
                    label.setPalette(odd_palette)
                else:
                    label.setPalette(even_palette)

                label.setAutoFillBackground(True)

        self.plot_timer = QTimer(self)
        self.plot_timer.start(100)

        for i in range(23):
            self.data_plot_widget.append(PlotWidget("",
                                                    [("", self.data_color[i][0], self.sensor_data[i], str)],
                                                    clear_button=self.clear_graphs,
                                                    x_scale_visible=False,
                                                    y_scale_visible=False,
                                                    curve_outer_border_visible=False,
                                                    curve_motion_granularity=0.1,
                                                    canvas_color=self.data_color[i][1],
                                                    external_timer=self.plot_timer,
                                                    curve_start='right',
                                                    key=None,
                                                    y_resolution=0.01))

        for w in self.data_plot_widget:
            w.setMinimumHeight(15)
            w.setMaximumHeight(25)

        for i in range(23):
            self.data_grid.addWidget(self.data_plot_widget[i], i, 4)

        self.data_grid.setColumnMinimumWidth(2, 75)

        self.gl_layout = QVBoxLayout()
        self.gl_layout.addWidget(self.imu_gl)
        self.layout_bottom.addLayout(self.gl_layout)
        self.save_orientation.clicked.connect(self.save_orientation_clicked)
        self.button_detach_3d_view.clicked.connect(self.detach_3d_view_clicked)

        self.checkbox_leds.stateChanged.connect(self.led_clicked)
        self.button_calibration.clicked.connect(self.calibration_clicked)
        self.calibration_color = [Qt.red, QColor(0xFF, 0xA0, 0x00), Qt.yellow, Qt.darkGreen]

        self.calibration = None
        self.alive = True
        self.callback_counter = 0

        self.status_led_action = QAction('Status LED', self)
        self.status_led_action.setCheckable(True)
        self.status_led_action.toggled.connect(lambda checked: self.imu.enable_status_led() if checked else self.imu.disable_status_led())
        self.set_configs([(0, None, [self.status_led_action])])

        reset = QAction('Reset', self)
        reset.triggered.connect(self.imu.reset)
        self.set_actions([(0, None, [reset])])

    def save_orientation_clicked(self):
        self.imu_gl.save_orientation()
        if self.imu_gl_wrapper is not None:
            self.imu_gl_wrapper.glWidget.save_orientation()
        self.orientation_label.hide()

    def cleanup_gl(self):
        self.state = self.imu_gl.get_state()
        self.imu_gl.hide()
        self.imu_gl.cleanup()

    def restart_gl(self):
        self.imu_gl = IMUV23DWidget()

        self.imu_gl.setFixedSize(200, 200)
        self.gl_layout.addWidget(self.imu_gl)
        self.imu_gl.show()

        self.save_orientation.clicked.connect(self.save_orientation_clicked)
        self.imu_gl.set_state(self.state)

    def start(self):
        if not self.alive:
            return

        self.parent().add_callback_on_untab(lambda x: self.cleanup_gl(), 'imu_v2_cleanup_on_untab')
        self.parent().add_callback_post_untab(lambda x: self.restart_gl(), 'imu_v2_restart_post_untab')
        self.parent().add_callback_on_tab(lambda x: self.cleanup_gl(), 'imu_v2_cleanup_on_tab')
        self.parent().add_callback_post_tab(lambda x: self.restart_gl(), 'imu_v2_restart_post_tab')

        self.gl_layout.activate()

        async_call(self.imu.is_status_led_enabled, None, self.status_led_action.setChecked, self.increase_error_count)
        async_call(self.imu.are_leds_on, None, self.checkbox_leds.setChecked, self.increase_error_count)

        self.cbe_all_data.set_period(50)

        for w in self.data_plot_widget:
            w.stop = False

    def stop(self):
        for w in self.data_plot_widget:
            w.stop = True

        if self.imu_gl_wrapper == None:
            self.cbe_all_data.set_period(0)

    def destroy(self):
        self.alive = False

        self.cleanup_gl()
        # Stop callback to fix deadlock with callback emulation thread.
        self.cbe_all_data.set_period(0)

        if self.calibration != None:
            self.calibration.close()

        if self.imu_gl_wrapper != None:
            self.imu_gl_wrapper.close()

    @staticmethod
    def has_device_identifier(device_identifier):
        return device_identifier == BrickIMUV2.DEVICE_IDENTIFIER

    def calibration_clicked(self):
        if self.calibration is None:
            self.calibration = Calibration(self)

        self.button_calibration.setEnabled(False)
        self.calibration.show()

    def detach_3d_view_clicked(self):
        if self.imu_gl_wrapper != None:
            self.imu_gl_wrapper.close()

        self.button_detach_3d_view.setEnabled(False)

        self.imu_gl_wrapper = WrapperWidget(self)
        self.imu_gl_wrapper.glWidget.set_state(self.imu_gl.get_state())
        self.save_orientation.clicked.connect(self.save_orientation_clicked)

        self.imu_gl_wrapper.show()

    def cb_all_data(self, data):
        self.callback_counter += 1

        if self.callback_counter == 2:
            self.callback_counter = 0

            self.sensor_data[0].value  = data.acceleration[0] / 100.0
            self.sensor_data[1].value  = data.acceleration[1] / 100.0
            self.sensor_data[2].value  = data.acceleration[2] / 100.0
            self.sensor_data[3].value  = data.magnetic_field[0] / 16.0
            self.sensor_data[4].value  = data.magnetic_field[1] / 16.0
            self.sensor_data[5].value  = data.magnetic_field[2] / 16.0
            self.sensor_data[6].value  = data.angular_velocity[0] / 16.0
            self.sensor_data[7].value  = data.angular_velocity[1] / 16.0
            self.sensor_data[8].value  = data.angular_velocity[2] / 16.0
            self.sensor_data[9].value  = data.euler_angle[0] / 16.0
            self.sensor_data[10].value = data.euler_angle[1] / 16.0
            self.sensor_data[11].value = data.euler_angle[2] / 16.0
            self.sensor_data[12].value = data.quaternion[0] / (2 ** 14 - 1)
            self.sensor_data[13].value = data.quaternion[1] / (2 ** 14 - 1)
            self.sensor_data[14].value = data.quaternion[2] / (2 ** 14 - 1)
            self.sensor_data[15].value = data.quaternion[3] / (2 ** 14 - 1)
            self.sensor_data[16].value = data.linear_acceleration[0] / 100.0
            self.sensor_data[17].value = data.linear_acceleration[1] / 100.0
            self.sensor_data[18].value = data.linear_acceleration[2] / 100.0
            self.sensor_data[19].value = data.gravity_vector[0] / 100.0
            self.sensor_data[20].value = data.gravity_vector[1] / 100.0
            self.sensor_data[21].value = data.gravity_vector[2] / 100.0
            self.sensor_data[22].value = data.temperature

            for i in range(23):
                self.data_labels[i].setText("{0:.2f}".format(self.sensor_data[i].value))

            self.imu_gl.update_orientation(self.sensor_data[12].value,
                               self.sensor_data[13].value,
                               self.sensor_data[14].value,
                               self.sensor_data[15].value)

            if self.imu_gl_wrapper is not None:
                self.imu_gl_wrapper.glWidget.update_orientation(self.sensor_data[12].value,
                                                    self.sensor_data[13].value,
                                                    self.sensor_data[14].value,
                                                    self.sensor_data[15].value)

            cal_mag = data.calibration_status & 3
            cal_acc = (data.calibration_status & (3 << 2)) >> 2
            cal_gyr = (data.calibration_status & (3 << 4)) >> 4
            cal_sys = (data.calibration_status & (3 << 6)) >> 6

            if self.calibration != None:
                self.calibration.save_calibration.setEnabled(data.calibration_status == 0xFF)

                self.calibration.mag_color.set_color(self.calibration_color[cal_mag])
                self.calibration.acc_color.set_color(self.calibration_color[cal_acc])
                self.calibration.gyr_color.set_color(self.calibration_color[cal_gyr])
                self.calibration.sys_color.set_color(self.calibration_color[cal_sys])
        else:
            self.imu_gl.update_orientation(data.quaternion[0] / (2 ** 14 - 1),
                               data.quaternion[1] / (2 ** 14 - 1),
                               data.quaternion[2] / (2 ** 14 - 1),
                               data.quaternion[3] / (2 ** 14 - 1))

            if self.imu_gl_wrapper is not None:
                self.imu_gl_wrapper.glWidget.update_orientation(data.quaternion[0] / (2 ** 14 - 1),
                                                    data.quaternion[1] / (2 ** 14 - 1),
                                                    data.quaternion[2] / (2 ** 14 - 1),
                                                    data.quaternion[3] / (2 ** 14 - 1))

    def led_clicked(self, state):
        if state == Qt.Checked:
            self.imu.leds_on()
        else:
            self.imu.leds_off()
class SinkSelectorSettings(SettingsSection):

    NAME = 'Multi-Sink Selector'
    ELEMENT = SinkSelector

    Sinks = elements.outputs()

    def __init__(self, size, Id, parent=None):
        super().__init__(size, parent)

        self.id = Id
        self._sinks = []

        self.groupBox = QGroupBox(self)
        self.groupBox.setTitle('Add/Remove outputs')
        self.groupBox.resize(self.width(), self.height())

        self.vlayout = QVBoxLayout(self.groupBox)
        self.vlayout.setContentsMargins(0, 5, 0, 0)

        self.sinksList = QListWidget(self.groupBox)
        self.sinksList.setAlternatingRowColors(True)
        self.vlayout.addWidget(self.sinksList)

        self.buttons = QDialogButtonBox(self.groupBox)
        self.vlayout.addWidget(self.buttons)

        self.add = QPushButton('Add')
        self.remove = QPushButton('Remove')
        self.buttons.addButton(self.add, QDialogButtonBox.YesRole)
        self.buttons.addButton(self.remove, QDialogButtonBox.NoRole)

        self.vlayout.activate()

        self.add.clicked.connect(self._new_sink)
        self.remove.clicked.connect(self.__remove_sink)

    def set_configuration(self, conf):
        if conf is not None and self.id in conf:
            conf = deepcopy(conf)
            for key in conf[self.id]:
                self.__add_sink(conf[self.id][key], key)
        else:
            self.__add_sink({'name': 'AutoSink'}, 'sink0')

    def get_configuration(self):
        conf = {}

        if not (self.groupBox.isCheckable() and not self.groupBox.isChecked()):
            conf[self.id] = {}
            for widget, name in self._sinks:
                conf[self.id][name] = widget.get_settings()

        return conf

    def enable_check(self, enable):
        self.groupBox.setCheckable(enable)
        self.groupBox.setChecked(False)

    def _new_sink(self):
        sinks = sorted(self.Sinks.keys())
        sinks.remove('SinkSelector')
        name, ok = QInputDialog.getItem(self, "Output", "Select output device",
                                        sinks, editable=False)
        if ok:
            self.__add_sink({'name': name})

    def __new_name(self):
        suff = 0

        for _, name in sorted(self._sinks, key=itemgetter(1)):
            if 'sink' + str(suff) != name:
                break
            suff += 1

        return 'sink' + str(suff)

    def __add_sink(self, properties, name=None):
        widget = OutputWidget(properties, self.sinksList)
        widget.resize(self.sinksList.width() - 5, 80)

        item = QListWidgetItem()
        item.setSizeHint(widget.size())

        if name is None:
            name = self.__new_name()

        self._sinks.append((widget, name))
        self.sinksList.addItem(item)
        self.sinksList.setItemWidget(item, widget)

        self.remove.setEnabled(len(self._sinks) > 1)

    def __remove_sink(self):
        self._sinks.pop(self.sinksList.currentRow())
        self.sinksList.removeItemWidget(self.sinksList.currentItem())
        self.sinksList.takeItem(self.sinksList.currentRow())

        self.remove.setEnabled(len(self._sinks) > 1)