class Example(QtGui.QWidget): def __init__(self): super(Example, self).__init__() self.initUI() self.current_y = 0 self.timer = QtCore.QTimer() self.timer.timeout.connect(self.update_plot) self.timer.setInterval(1000 / FREQ) self.timer.start() def initUI(self): self.setGeometry(300, 300, 1000, 1000) self.setWindowTitle('Icon') self.setWindowIcon(QtGui.QIcon('web.png')) self.plot = PlotWidget( self, axisItems={'bottom': TimeAxisItem(orientation='bottom')}) self.plot.resize(900, 900) self.curve = self.plot.plot(x, ys[0]) #self.curve.attach(self.plot) self.show() def update_plot(self): self.curve.setData(x, ys[self.current_y]) self.current_y = (self.current_y + 1) % YS
class Example(QtGui.QWidget): def __init__(self): super(Example, self).__init__() self.initUI() self.current_y = 0 self.timer = QtCore.QTimer() self.timer.timeout.connect(self.update_plot) self.timer.setInterval(1000/FREQ) self.timer.start() def initUI(self): self.setGeometry(300, 300, 1000, 1000) self.setWindowTitle('Icon') self.setWindowIcon(QtGui.QIcon('web.png')) self.plot = PlotWidget(self, axisItems={'bottom': TimeAxisItem(orientation='bottom')}) self.plot.resize(900, 900) self.curve = self.plot.plot(x, ys[0]) #self.curve.attach(self.plot) self.show() def update_plot(self): self.curve.setData(x, ys[self.current_y]) self.current_y = (self.current_y + 1) % YS
class Example(QtGui.QWidget): def __init__(self): super(Example, self).__init__() self.initUI() self.current_y = 0 #self.timer = QtCore.QTimer() #self.timer.timeout.connect(self.update_plot) #self.timer.setInterval(1000/FREQ) #self.timer.start() def initUI(self): self.setGeometry(300, 300, 1000, 1000) self.setWindowTitle('Icon') self.setWindowIcon(QtGui.QIcon('web.png')) self.plot = PlotWidget(self) self.plot.resize(900, 900) self.curves = [] for x, y in zip(chunkedx, chunkedy): print('plot', len(x), len(y)) self.curves.append(self.plot.plot(x, y)) print(self.curves[-1]) self.show() @timeme def update_plot(self): self.curves[-1].setData(small_x, ys[self.current_y]) self.current_y = (self.current_y + 1) % YS
class Example(QtGui.QWidget): def __init__(self): super(Example, self).__init__() self.setGeometry(300, 300, 400, 400) self.plot = PlotWidget(self, axisItems={'bottom': TimeAxisItem(orientation='bottom')}) self.plot.resize(300, 300) self.curve = self.plot.plot(np.linspace(0, 10, 100), np.random.random(100)) self.show()
class Example(QtGui.QWidget): def __init__(self): super(Example, self).__init__() self.setGeometry(300, 300, 400, 400) self.plot = PlotWidget( self, axisItems={'bottom': TimeAxisItem(orientation='bottom')}) self.plot.resize(300, 300) self.curve = self.plot.plot(np.linspace(0, 10, 100), np.random.random(100)) self.show()
class pyviViewerWindow(QtGui.QMainWindow): def __init__(self, flowchart): QtGui.QMainWindow.__init__(self) self.resize(1000, 800) self.setWindowTitle('Viewer') self.fc = flowchart self.fc.sigChartChanged.connect(self.nodeEvent) self.fc.sigChartLoaded.connect(self.loadChartEvent) self.fc.widget().resize(1000, 800) self.pyviwin = SimpleWindow() ## http://blog.qt.io/blog/2013/02/19/introducing-qwidgetcreatewindowcontainer/ pyvi_widget = QWidget.createWindowContainer(self.pyviwin) pyvi_widget.setMinimumSize(200, 200) pyvi_widget.resize(800, 800) self.setCentralWidget(pyvi_widget) self.layerDockWidget = QDockWidget('Layers') self.layerDockWidget.setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable) self.layerDockWidget.setWidget(self.pyviwin.layerWidget) self.pyviwin.layerWidget.resize(200, 500) self.addDockWidget(Qt.RightDockWidgetArea, self.layerDockWidget) self.plotDockWidget = QDockWidget('Plot') self.plotDockWidget.setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetClosable) self.plotWidget = PlotWidget() self.plotDockWidget.setWidget(self.plotWidget) self.plotWidget.resize(100, 100) # self.plotDockWidget.show() # self.addDockWidget(Qt.RightDockWidgetArea, self.plotDockWidget) self.plotList = {'plot 1': self.plotWidget} self.plot3DDockWidget = QDockWidget('Plot3D') self.plot3DDockWidget.setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetClosable) self.pyviwin_plot = SimpleWindow() pyviwin_plot_widget = QWidget.createWindowContainer(self.pyviwin_plot) pyviwin_plot_widget.setMinimumSize(100, 100) pyviwin_plot_widget.resize(200, 200) self.plot3DDockWidget.setWidget(pyviwin_plot_widget) self.plot3DDockWidget.show() # self.plot3D.resize(100,100) self.plotLayerDockWidget = QDockWidget('Plot Layers') self.plotLayerDockWidget.setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable) self.plotLayerDockWidget.setWidget(self.pyviwin_plot.layerWidget) self.pyviwin_plot.layerWidget.resize(200, 500) self.addDockWidget(Qt.RightDockWidgetArea, self.plotLayerDockWidget) # self.dockWidgetDict = {} self.createActions() self.createMenus() self.show() self.fc.widget().show() def save(self): try: self.fc.saveFile() except: raise def open(self): self.fc.loadFile() def createActions(self): self.saveAct = QAction("&Save...", self, shortcut=QtGui.QKeySequence.Save, statusTip="Save the current form letter", triggered=self.save) self.openAct = QAction("&Open...", self, shortcut=QtGui.QKeySequence.Open, statusTip="Save the current form letter", triggered=self.open) self.reloadAct = QAction("&Reload nodelibs...", self, shortcut=QtGui.QKeySequence.Refresh, statusTip="Reload nodelibraries", triggered=self.fc.widget().reloadLibrary) self.quitAct = QAction("&Quit", self, shortcut=QtGui.QKeySequence.Quit, statusTip="Quit the application", triggered=self.close) self.toggleViewerAct = QAction("&Toggle Viewer", self, statusTip="Toggle viewer window", triggered=self.toggleViewer) self.toggleFlowchartAct = QAction("&Toggle Flowchart", self, statusTip="Toggle flowchart window", triggered=self.toggleFlowchart) self.togglePlotterAct = QAction("&Toggle Plotter", self, statusTip="Toggle plot window", triggered=self.togglePlotter) def toggleViewer(self): if self.isHidden(): self.show() else: self.hide() def togglePlotter(self): if self.plotDockWidget.isHidden(): self.plotDockWidget.show() else: self.plotDockWidget.hide() def toggleFlowchart(self): if self.fc.widget().isHidden(): self.fc.widget().show() else: self.fc.widget().hide() def createMenus(self): self.fileMenu = self.menuBar().addMenu("&File") self.fileMenu.addAction(self.openAct) self.fileMenu.addAction(self.saveAct) self.fileMenu.addAction(self.reloadAct) # self.fileMenu.addSeparator() # self.fileMenu.addAction(self.quitAct) # self.editMenu = self.menuBar().addMenu("&Edit") # self.editMenu.addAction(self.undoAct) self.viewMenu = self.menuBar().addMenu("&View") self.viewMenu.addAction(self.toggleViewerAct) self.viewMenu.addAction(self.toggleFlowchartAct) self.viewMenu.addAction(self.togglePlotterAct) def loadChartEvent(self): # self.clearDock() for node in self.fc.nodes().values(): self.addNode(node) def nodeEvent(self, flowchart, action, node): if action == 'add': self.addNode(node) elif action == 'remove': self.removeNode(node) elif action == 'rename': pass # try: # self.dockWidgetDict[node].setWindowTitle(node.name()) # except KeyError: # pass def addNode(self, node): # ctrlWidget = node.ctrlWidget() # if ctrlWidget: # dock = QDockWidget(node.name(), self) # dock.setWidget(node.ctrlWidget()) # dock.hide() # # nodeDock.hideTitleBar() # # self.da.addDock(nodeDock, 'right', ) # self.addDockWidget(Qt.RightDockWidgetArea, dock) # self.viewMenu.addAction(dock.toggleViewAction()) # self.dockWidgetDict[node] = dock if type(node) is pvWindowNode: node.setPyViWindow(self.pyviwin, self.pyviwin_plot) elif type(node) is PlotWidgetNode: node.setPlotList(self.plotList) node.setPlot(self.plotWidget) def removeNode(self, node): print("removing...", node)
class DirectDriveWindow(QWidget): virtual_controller = None # will hold an instance of the VirtualController-class motor_controller = None # will hold an instance of the MotorController-class game_interfacer = None # will hold an instance of the GameInterfacer-class # Variables that will be shared between multiple processes raw_force_signal_global = Value( 'f', 0.0) # raw force-feedback value coming from the game game_status_playing_global = Value( 'b', False) # game status (playing/not-playing) motor_pause_mode = Value( 'b', False) # if the motor should be idle when game is paused data_reconstruction = Value( 'b', False) # if the data-reconstruction feature should be used data_smoothing_level = Value( 'i', 0) # if the temproal smoothing feature should be used friction_global = Value('f', 0.0) # strength of the friction effect damping_global = Value('f', 0.0) # strength of the damping effect inertia_global = Value('f', 0.0) # strength of the inertia effect encoder_pos_global = Value( 'f', 0.0) # holds the wheel position in encoder counts wheel_position_deg_global = Value( 'f', 0.0) # holds the wheel position in degrees lock_to_lock_global = Value( 'f', 900.0 ) # holds the digital limit of the wheel (used by the virtual controller) bump_to_bump_global = Value( 'f', 900.0 ) # holds the physical limit of the wheel (used by the motor controller) motor_current_global = Value( 'f', 0.0 ) # holds the current(Ampere) that is applied to the motor at any moment actual_max_force_global = Value( 'f', 0.0 ) # sets the actual maximum torque(Nm) the motor should be limited to invert_force_global = Value( 'b', False) # if the raw force-feedback value should be inverted motor_connected_global = Value( 'b', False) # motor status (connected/not-connected) motor_controller_due_for_restart_global = Value( 'b', False) # is set to true when connection to odrive board is lost motor_control_rate = Value( 'f', 0.0) # target refresh rate for the motor-control process profiles = [] # holds all loaded and newly created profiles selected_profile_index = 0 # holds the index of the currently selected profile graph_sample_rate = 60 # update-rate(Hz) for the motor-current graph graph_history_time_sec = 2 # time-window of the motor-current graph def __init__(self): super().__init__() self.setGeometry(100, 100, 1700, 575) self.setWindowTitle('DIY Direct Drive Wheel') #self.updater = LiveGuiUpdater() #self.updater.ticked.connect(self.LiveGuiTick) #self.updater.start() self.timer = QTimer() self.timer.timeout.connect(self.graph_update) self.timer.setInterval(int(1000 / self.graph_sample_rate)) self.timer.start() self.gui_timer = QTimer() self.gui_timer.timeout.connect(self.LiveGuiTick) self.gui_timer.setInterval(int(1000 / 60)) self.gui_timer.start() self.virtual_controller = VirtualController() self.virtual_controller.set_frequency(180) self.virtual_controller.steering_value_deg = self.wheel_position_deg_global self.virtual_controller.lock_to_lock = self.lock_to_lock_global self.virtual_controller.start() self.game_interfacer = GameInterfacer() self.game_interfacer.set_frequency(180) self.game_interfacer.wheel_force = self.raw_force_signal_global self.game_interfacer.invert_force = self.invert_force_global self.game_interfacer.game_status_playing = self.game_status_playing_global self.motor_controller = MotorController() self.motor_controller.set_frequency(360) self.motor_controller.signal_in = self.raw_force_signal_global self.motor_controller.game_status_playing = self.game_status_playing_global self.motor_controller.motor_pause_mode = self.motor_pause_mode self.motor_controller.data_reconstruction = self.data_reconstruction self.motor_controller.data_smoothing_level = self.data_smoothing_level self.motor_controller.friction = self.friction_global self.motor_controller.damping = self.damping_global self.motor_controller.inertia = self.inertia_global self.motor_controller.encoder_pos = self.encoder_pos_global self.motor_controller.wheel_pos_deg = self.wheel_position_deg_global self.motor_controller.bump_to_bump = self.bump_to_bump_global self.motor_controller.motor_current = self.motor_current_global self.motor_controller.actual_max_force = self.actual_max_force_global self.motor_controller.motor_connected = self.motor_connected_global self.motor_controller.due_for_restart = self.motor_controller_due_for_restart_global self.motor_controller.achieved_freq = self.motor_control_rate self.motor_controller.start() self.lblMotorRate = QLabel('motor control rate:', self) self.lblMotorRate.setFont(QFont("Times", 24, QFont.Normal)) self.lblMotorRate.move(675, 25) self.lblMotorRateHz = QLabel('0.00 Hz', self) self.lblMotorRateHz.setFont(QFont("Times", 24, QFont.Normal)) self.lblMotorRateHz.adjustSize() self.lblMotorRateHz.move(1125 - self.lblMotorRateHz.width(), 25) self.force_graph = PlotWidget(self) self.force_graph.move(675, 75) self.force_graph.resize(1000, 475) self.force_graph.setYRange(-15.0, 15.0) self.force_graph.setBackground((255, 255, 255)) self.force_graph.showGrid(x=False, y=True) self.force_graph.addLegend() pen_ffb = pg.mkPen(color=(0, 0, 255)) self.ffb_curve = self.force_graph.getPlotItem().plot( pen=pen_ffb, name='Game FFB (Nm)') pen_current = pg.mkPen(color=(0, 255, 0)) self.current_curve = self.force_graph.getPlotItem().plot( pen=pen_current, name='Motor Torque (Nm)') self.ffb_history = [ 0 ] * self.graph_sample_rate * self.graph_history_time_sec self.current_history = [ 0 ] * self.graph_sample_rate * self.graph_history_time_sec self.load_profiles_from_file() self.build_gui() self.select_profile(0) # updates the graph showing the raw ffb and the motor torque def graph_update(self): # adding the latest raw game-ffb value(scaled to the actual torque value) to a list storing a history of this value self.ffb_history.append(self.raw_force_signal_global.value / self.actual_max_force_global.value) if len(self.ffb_history ) >= self.graph_history_time_sec * self.graph_sample_rate: self.ffb_history.pop(0) self.ffb_curve.setData(self.ffb_history) # adding the latest final motor torque value to a list storing a history of this value self.current_history.append( self.motor_current_global.value / (self.actual_max_force_global.value * 2.75)) if len(self.current_history ) >= self.graph_history_time_sec * self.graph_sample_rate: self.current_history.pop(0) self.current_curve.setData(self.current_history) # displaying the actually achieved refresh rate of the motor-control process self.lblMotorRateHz.setText( str(int(self.motor_control_rate.value * 100) / 100) + ' Hz') if self.motor_control_rate.value >= 360.0: self.lblMotorRateHz.setStyleSheet( 'QLabel { color: rgb(0,155,0); }') else: self.lblMotorRateHz.setStyleSheet( 'QLabel { color: rgb(155,0,0); }') # initializes and configures all the GUI-elements def build_gui(self): # WHEEL POSITION DATA AND SETTINGS # current bar self.lblCurrent = QLabel(self) self.lblCurrent.move(25 + 128, 25) self.lblCurrent.resize(0, 25) self.lblCurrent.setAutoFillBackground(True) self.lblCurrent.setStyleSheet( 'QLabel { background-color: rgb(0,255,0); }') self.lblCurrentDivider = QLabel(self) self.lblCurrentDivider.move(25 + 128 - 1, 20) self.lblCurrentDivider.resize(2, 35) self.lblCurrentDivider.setAutoFillBackground(True) self.lblCurrentDivider.setStyleSheet( 'QLabel { background-color: rgb(0,0,0); }') # wheel image self.lblWheelImage = QLabel(self) self.wheel_image = QImage('wheel_image.png') self.wheel_image_discon = QImage('wheel_image_disconnect.png') self.wheel_pixmap = QPixmap.fromImage(self.wheel_image) self.lblWheelImage.setPixmap(self.wheel_pixmap) self.lblWheelImage.resize(self.wheel_pixmap.width(), self.wheel_pixmap.height()) self.lblWheelImage.move(25, 75) # motor status label self.lblMotorStatus_1 = QLabel('Motor Status:', self) self.lblMotorStatus_1.move(15, 60) self.lblMotorStatus_2 = QLabel('Not Connected', self) self.lblMotorStatus_2.move(15, 75) self.lblMotorStatus_2.setStyleSheet('QLabel { color: rgb(155,0,0); }') # wheel position self.lblWheelPos = QLabel('0.0°', self) self.lblWheelPos.move(25 + 150 + 25, 75 + 256 + 10) self.lblWheelPos.resize(200, 50) self.lblWheelPos.setFont(QFont("Times", 24, QFont.Normal)) # center button center_pos = [25, 75 + 256 + 22, 150, 30] self.btnCenter = QPushButton('center Wheel', self) self.btnCenter.move(center_pos[0], center_pos[1]) self.btnCenter.resize(center_pos[2], center_pos[3]) self.btnCenter.clicked.connect(self.center_wheel) # lock-to-lock slider self.locklock_pos = [25, 425, 250, 0] self.lblLock = QLabel('Lock to Lock:', self) self.lblLock.move(self.locklock_pos[0], self.locklock_pos[1]) self.sliLock = QSlider(Qt.Horizontal, self) self.sliLock.setTickPosition(QSlider.TicksAbove) self.sliLock.setTickInterval(10) self.sliLock.setMinimum(10) self.sliLock.setMaximum(120) self.sliLock.setValue(90) self.sliLock.setSingleStep(1) self.sliLock.move(self.locklock_pos[0], self.locklock_pos[1] + 20) self.sliLock.resize(self.locklock_pos[2], 25) self.sliLock.valueChanged.connect(self.change_lock) self.lblLockVal = QLabel('900°', self) self.lblLockVal.adjustSize() self.lblLockVal.move( self.locklock_pos[0] + self.locklock_pos[2] - self.lblLockVal.width(), self.locklock_pos[1]) # bump_to_bump slider self.bumpbump_pos = [25, 500, 250, 0] self.lblBump = QLabel('Bump to Bump:', self) self.lblBump.move(self.bumpbump_pos[0], self.bumpbump_pos[1]) self.sliBump = QSlider(Qt.Horizontal, self) self.sliBump.setTickPosition(QSlider.TicksAbove) self.sliBump.setTickInterval(10) self.sliBump.setMinimum(10) self.sliBump.setMaximum(120) self.sliBump.setValue(90) self.sliBump.setSingleStep(1) self.sliBump.move(self.bumpbump_pos[0], self.bumpbump_pos[1] + 20) self.sliBump.resize(self.bumpbump_pos[2], 25) self.sliBump.valueChanged.connect(self.change_bump) self.lblBumpVal = QLabel('900°', self) self.lblBumpVal.adjustSize() self.lblBumpVal.move( self.bumpbump_pos[0] + self.bumpbump_pos[2] - self.lblBumpVal.width(), self.bumpbump_pos[1]) # WHEEL FORCE AND BEHAVIOR SETTINGS # game select combobox gameselect_pos = [325, 25, 250, 25] self.cbxGame = QComboBox(self) self.cbxGame.move(gameselect_pos[0], gameselect_pos[1]) self.cbxGame.resize(gameselect_pos[2], gameselect_pos[3]) for game in self.game_interfacer.games_available: self.cbxGame.addItem(game) self.cbxGame.activated.connect(self.select_game) # invert force checkbox invert_pos = [325, 55] self.cbInvert = QCheckBox('Invert Force Feedback', self) self.cbInvert.move(invert_pos[0], invert_pos[1]) self.cbInvert.stateChanged.connect(self.toggle_invert) # motor pause mode checkbox motorpause_pos = [325, 85] self.cbMotorPause = QCheckBox('pause motor when game is paused', self) self.cbMotorPause.move(motorpause_pos[0], motorpause_pos[1]) self.cbMotorPause.stateChanged.connect(self.toggle_motor_pause_mode) self.cbMotorPause.toggle() # maximum force slider self.max_force_pos = [325, 125, 250, 0] self.lblMaxForce = QLabel('Maximum Force in Nm:', self) self.lblMaxForce.move(self.max_force_pos[0], self.max_force_pos[1]) self.sliMaxForce = QSlider(Qt.Horizontal, self) self.sliMaxForce.setTickPosition(QSlider.TicksAbove) self.sliMaxForce.setTickInterval(10) self.sliMaxForce.setMinimum(1) self.sliMaxForce.setMaximum(150) self.sliMaxForce.setValue(0) self.sliMaxForce.setSingleStep(1) self.sliMaxForce.move(self.max_force_pos[0], self.max_force_pos[1] + 20) self.sliMaxForce.resize(self.max_force_pos[2], 25) self.sliMaxForce.valueChanged.connect(self.change_max_force) self.lblMaxForceVal = QLabel('0.0 Nm - (0.0 Amp)', self) self.lblMaxForceVal.adjustSize() self.lblMaxForceVal.move( self.max_force_pos[0] + self.max_force_pos[2] - self.lblMaxForceVal.width(), self.max_force_pos[1]) # reconstruction checkbox reconst_pos = [325, 175 + 3] self.cbReconst = QCheckBox('Use Data Reconstruction', self) self.cbReconst.move(reconst_pos[0], reconst_pos[1]) self.cbReconst.stateChanged.connect(self.toggle_reconstruction) self.cbReconst.setEnabled(False) # smoothing level combobox smoothing_pos = [475, 175, 100, 25] self.cbxSmoothing = QComboBox(self) self.cbxSmoothing.move(smoothing_pos[0], smoothing_pos[1]) self.cbxSmoothing.resize(smoothing_pos[2], smoothing_pos[3]) self.cbxSmoothing.addItem('no Smoothing') self.cbxSmoothing.addItem('1') self.cbxSmoothing.addItem('2') self.cbxSmoothing.addItem('3') self.cbxSmoothing.addItem('4') self.cbxSmoothing.addItem('5') self.cbxSmoothing.addItem('6') self.cbxSmoothing.addItem('7') self.cbxSmoothing.addItem('8') self.cbxSmoothing.addItem('9') self.cbxSmoothing.activated.connect(self.change_smoothing) self.cbxSmoothing.setEnabled(False) # friction slider self.friction_pos = [325, 215, 250, 0] self.lblFriction = QLabel('Friction:', self) self.lblFriction.move(self.friction_pos[0], self.friction_pos[1]) self.sliFriction = QSlider(Qt.Horizontal, self) self.sliFriction.setTickPosition(QSlider.TicksAbove) self.sliFriction.setTickInterval(10) self.sliFriction.setMinimum(0) self.sliFriction.setMaximum(100) self.sliFriction.setValue(0) self.sliFriction.setSingleStep(1) self.sliFriction.move(self.friction_pos[0], self.friction_pos[1] + 20) self.sliFriction.resize(self.friction_pos[2], 25) self.sliFriction.valueChanged.connect(self.change_friction) self.lblFrictionVal = QLabel('0 %', self) self.lblFrictionVal.adjustSize() self.lblFrictionVal.move( self.friction_pos[0] + self.friction_pos[2] - self.lblFrictionVal.width(), self.friction_pos[1]) # damping slider self.damping_pos = [325, 275, 250, 0] self.lblDamping = QLabel('Damping:', self) self.lblDamping.move(self.damping_pos[0], self.damping_pos[1]) self.sliDamping = QSlider(Qt.Horizontal, self) self.sliDamping.setTickPosition(QSlider.TicksAbove) self.sliDamping.setTickInterval(10) self.sliDamping.setMinimum(0) self.sliDamping.setMaximum(100) self.sliDamping.setValue(0) self.sliDamping.setSingleStep(1) self.sliDamping.move(self.damping_pos[0], self.damping_pos[1] + 20) self.sliDamping.resize(self.damping_pos[2], 25) self.sliDamping.valueChanged.connect(self.change_damping) self.lblDampingVal = QLabel('0 %', self) self.lblDampingVal.adjustSize() self.lblDampingVal.move( self.damping_pos[0] + self.damping_pos[2] - self.lblDampingVal.width(), self.damping_pos[1]) # inertia slider self.inertia_pos = [325, 335, 250, 0] self.lblInertia = QLabel('Inertia:', self) self.lblInertia.move(self.inertia_pos[0], self.inertia_pos[1]) self.sliInertia = QSlider(Qt.Horizontal, self) self.sliInertia.setTickPosition(QSlider.TicksAbove) self.sliInertia.setTickInterval(10) self.sliInertia.setMinimum(0) self.sliInertia.setMaximum(100) self.sliInertia.setValue(0) self.sliInertia.setSingleStep(1) self.sliInertia.move(self.inertia_pos[0], self.inertia_pos[1] + 20) self.sliInertia.resize(self.inertia_pos[2], 25) self.sliInertia.valueChanged.connect(self.change_inertia) self.lblInertiaVal = QLabel('0 %', self) self.lblInertiaVal.adjustSize() self.lblInertiaVal.move( self.inertia_pos[0] + self.inertia_pos[2] - self.lblInertiaVal.width(), self.inertia_pos[1]) # profile select combobox profileselect_pos = [325, 430, 250, 25] self.cbxProfiles = QComboBox(self) self.cbxProfiles.move(profileselect_pos[0], profileselect_pos[1]) self.cbxProfiles.resize(profileselect_pos[2], profileselect_pos[3]) for profile in self.profiles: self.cbxProfiles.addItem(profile.name) self.cbxProfiles.activated.connect(self.select_profile) # create profile button profilecreate_pos = [325, 460, 250, 25] self.btnCreate = QPushButton('create new profile', self) self.btnCreate.move(profilecreate_pos[0], profilecreate_pos[1]) self.btnCreate.resize(profilecreate_pos[2], profilecreate_pos[3]) self.btnCreate.clicked.connect(self.create_profile) # rename profile button profilerename_pos = [325, 490, 250, 25] self.btnRename = QPushButton('rename profile', self) self.btnRename.move(profilerename_pos[0], profilerename_pos[1]) self.btnRename.resize(profilerename_pos[2], profilerename_pos[3]) self.btnRename.clicked.connect(self.rename_profile) # cancel rename button cancelrename_pos = [325 + 200, 490, 50, 25] self.btnCancelRename = QPushButton('cancel', self) self.btnCancelRename.move(cancelrename_pos[0], cancelrename_pos[1]) self.btnCancelRename.resize(cancelrename_pos[2], cancelrename_pos[3]) self.btnCancelRename.clicked.connect(self.cancel_rename) self.btnCancelRename.setVisible(False) # rename textbox self.txtRename = QLineEdit(self) self.txtRename.move(profilerename_pos[0], profilerename_pos[1]) self.txtRename.resize(profilerename_pos[2] - 35 - 55, profilerename_pos[3]) self.txtRename.setVisible(False) self.txtRename.textChanged.connect(self.check_name_validity) # save profile button profilesave_pos = [325, 520, 120, 25] self.btnSave = QPushButton('save profile', self) self.btnSave.move(profilesave_pos[0], profilesave_pos[1]) self.btnSave.resize(profilesave_pos[2], profilesave_pos[3]) self.btnSave.clicked.connect(self.save_current_profile_internally) # delete profile button profiledelete_pos = [325 + 130, 520, 120, 25] self.btnDelete = QPushButton('delete profile', self) self.btnDelete.move(profiledelete_pos[0], profiledelete_pos[1]) self.btnDelete.resize(profiledelete_pos[2], profiledelete_pos[3]) self.btnDelete.clicked.connect(self.delete_current_profile) # updates the GUI-elements that indicate the status of the motor (connected/not-connected) def LiveGuiTick(self): self.lblWheelPos.setText( str(int(self.wheel_position_deg_global.value * 10) / 10) + '°') self.lblCurrent.resize( int( np.abs(self.motor_current_global.value / (self.actual_max_force_global.value * 2.75) * 128)), 25) if self.motor_current_global.value < 0.0: self.lblCurrent.move( 25 + 128 - np.abs(self.motor_current_global.value / (self.actual_max_force_global.value * 2.75) * 128), 25) tf = QTransform() tf.rotate(self.wheel_position_deg_global.value) if self.motor_connected_global.value: self.lblMotorStatus_2.setText('Connected') self.lblMotorStatus_2.setStyleSheet( 'QLabel { color: rgb(0,155,0); }') img_rot = self.wheel_image.transformed(tf) self.btnCenter.setEnabled(True) self.lblWheelPos.setStyleSheet('QLabel { color: rgb(0,0,0); }') else: self.lblMotorStatus_2.setText('Not Connected') self.lblMotorStatus_2.setStyleSheet( 'QLabel { color: rgb(155,0,0); }') img_rot = self.wheel_image_discon.transformed(tf) self.btnCenter.setEnabled(False) self.lblWheelPos.setStyleSheet( 'QLabel { color: rgb(155,155,155); }') diagonal_overlength = (np.sqrt(np.square(256) * 2) - 256) / 2 shift = int( np.abs( np.sin(np.deg2rad(self.wheel_position_deg_global.value * 2)) * diagonal_overlength)) crop_rect = QRect(shift, shift, 256, 256) self.wheel_pixmap = QPixmap.fromImage(img_rot).copy(crop_rect) self.lblWheelImage.setPixmap(self.wheel_pixmap) if self.motor_controller_due_for_restart_global.value: self.motor_controller_due_for_restart_global.value = False print('restarting motor...') try: self.motor_controller.motor_control_process.terminate() except: pass self.motor_controller.start() # helper function to get the index of a profile with a certain name from the profile-list def get_profile_index(self, name): for i in range(len(self.profiles)): if self.profiles[i].name == name: return i # WHEEL POSITION SETTING FUNCTIONS def center_wheel(self): print('centering wheel') self.motor_controller.set_encoder_offset() def change_lock(self): val = int(self.sender().value() * 10) self.lblLockVal.setText(str(val) + '°') self.lock_to_lock_global.value = val self.lblLockVal.adjustSize() self.lblLockVal.move( self.locklock_pos[0] + self.locklock_pos[2] - self.lblLockVal.width(), self.locklock_pos[1]) def change_bump(self): val = int(self.sender().value() * 10) self.lblBumpVal.setText(str(val) + '°') self.bump_to_bump_global.value = val self.lblBumpVal.adjustSize() self.lblBumpVal.move( self.bumpbump_pos[0] + self.bumpbump_pos[2] - self.lblBumpVal.width(), self.bumpbump_pos[1]) # WHEEL FORCE SETTING FUNCTIONS def select_game(self, item_index): print('selecting', item_index) self.game_interfacer.terminate() time.sleep(1) self.game_interfacer.start(item_index) def toggle_invert(self, state): if state == Qt.Checked: self.invert_force_global.value = True else: self.invert_force_global.value = False def toggle_motor_pause_mode(self, state): if state == Qt.Checked: self.motor_pause_mode.value = True else: self.motor_pause_mode.value = False def change_max_force(self): val = self.sender().value() / 10 self.lblMaxForceVal.setText( str(val) + ' Nm - (' + str(int(val * 2.75 * 100) / 100) + ' Amp)') self.actual_max_force_global.value = val self.lblMaxForceVal.adjustSize() self.lblMaxForceVal.move( self.max_force_pos[0] + self.max_force_pos[2] - self.lblMaxForceVal.width(), self.max_force_pos[1]) self.force_graph.setYRange(-val, val) def change_friction(self): val = self.sender().value() / 100 self.lblFrictionVal.setText(str(int(val * 100)) + ' %') self.friction_global.value = val self.lblFrictionVal.adjustSize() self.lblFrictionVal.move( self.friction_pos[0] + self.friction_pos[2] - self.lblFrictionVal.width(), self.friction_pos[1]) def change_damping(self): val = self.sender().value() / 100 self.lblDampingVal.setText(str(int(val * 100)) + ' %') self.damping_global.value = val self.lblDampingVal.adjustSize() self.lblDampingVal.move( self.damping_pos[0] + self.damping_pos[2] - self.lblDampingVal.width(), self.damping_pos[1]) def change_inertia(self): val = self.sender().value() / 100 self.lblInertiaVal.setText(str(int(val * 100)) + ' %') self.inertia_global.value = val self.lblInertiaVal.adjustSize() self.lblInertiaVal.move( self.inertia_pos[0] + self.inertia_pos[2] - self.lblInertiaVal.width(), self.inertia_pos[1]) def toggle_reconstruction(self, state): if state == Qt.Checked: self.data_reconstruction.value = True else: self.data_reconstruction.value = False def change_smoothing(self, item_index): self.data_smoothing_level.value = item_index def select_profile(self, selected_profile): self.sliLock.setValue( int(self.profiles[selected_profile].lock_to_lock / 10)) self.sliBump.setValue( int(self.profiles[selected_profile].bump_to_bump / 10)) self.cbInvert.setChecked( bool(self.profiles[selected_profile].invert_force)) self.sliMaxForce.setValue( int(self.profiles[selected_profile].max_force * 10)) self.cbReconst.setChecked( bool(self.profiles[selected_profile].use_reconstruction)) self.cbxSmoothing.setCurrentIndex( int(self.profiles[selected_profile].smoothing_level)) self.sliFriction.setValue( int(self.profiles[selected_profile].friction * 100)) self.sliDamping.setValue( int(self.profiles[selected_profile].damping * 100)) self.sliInertia.setValue( int(self.profiles[selected_profile].inertia * 100)) def save_current_profile_internally(self): current_index = self.cbxProfiles.currentIndex() self.profiles[ current_index].lock_to_lock = self.lock_to_lock_global.value self.profiles[ current_index].bump_to_bump = self.bump_to_bump_global.value self.profiles[ current_index].invert_force = self.invert_force_global.value self.profiles[ current_index].max_force = self.actual_max_force_global.value self.profiles[ current_index].use_reconstruction = self.data_reconstruction.value self.profiles[ current_index].smoothing_level = self.data_smoothing_level.value self.profiles[current_index].friction = self.friction_global.value self.profiles[current_index].damping = self.damping_global.value self.profiles[current_index].inertia = self.inertia_global.value self.save_profiles_to_file() def save_profiles_to_file(self): root = ET.Element('profiles') for profile in self.profiles: prof_elem = ET.SubElement(root, 'profile', name=profile.name) ET.SubElement(prof_elem, 'lock_to_lock').text = str(profile.lock_to_lock) ET.SubElement(prof_elem, 'bump_to_bump').text = str(profile.bump_to_bump) ET.SubElement(prof_elem, 'invert_force').text = str( bool(profile.invert_force)) ET.SubElement(prof_elem, 'max_force').text = str(profile.max_force) ET.SubElement(prof_elem, 'use_reconstruction').text = str( bool(profile.use_reconstruction)) ET.SubElement(prof_elem, 'smoothing_level').text = str( profile.smoothing_level) ET.SubElement(prof_elem, 'friction').text = str(profile.friction) ET.SubElement(prof_elem, 'damping').text = str(profile.damping) ET.SubElement(prof_elem, 'inertia').text = str(profile.inertia) tree = ET.ElementTree(root) tree.write("profiles.xml") def load_profiles_from_file(self): self.profiles.clear() tree = ET.parse('profiles.xml') root = tree.getroot() for prof in root: profile = Profile(prof.attrib['name']) profile.lock_to_lock = float(prof[0].text) profile.bump_to_bump = float(prof[1].text) profile.invert_force = bool(prof[2].text == 'True') profile.max_force = float(prof[3].text) profile.use_reconstruction = bool(prof[4].text == 'True') profile.smoothing_level = int(prof[5].text) profile.friction = float(prof[6].text) profile.damping = float(prof[7].text) profile.inertia = float(prof[8].text) self.profiles.append(profile) def delete_current_profile(self): current_index = self.cbxProfiles.currentIndex() print('deleting profile at index:', current_index) self.profiles.pop(current_index) self.cbxProfiles.clear() for profile in self.profiles: self.cbxProfiles.addItem(profile.name) self.cbxProfiles.setCurrentIndex(0) self.select_profile(0) self.save_current_profile_internally() if len(self.profiles) == 1: self.btnDelete.setEnabled(False) def create_profile(self): new_profile = Profile('profile #' + str(len(self.profiles) + 1)) current_index = self.cbxProfiles.currentIndex() new_profile.lock_to_lock = self.profiles[current_index].lock_to_lock new_profile.bump_to_bump = self.profiles[current_index].bump_to_bump new_profile.invert_force = self.profiles[current_index].invert_force new_profile.max_force = self.profiles[current_index].max_force new_profile.use_reconstruction = self.profiles[ current_index].use_reconstruction new_profile.smoothing_level = self.profiles[ current_index].smoothing_level new_profile.friction = self.profiles[current_index].friction new_profile.damping = self.profiles[current_index].damping new_profile.inertia = self.profiles[current_index].inertia self.profiles.append(new_profile) self.cbxProfiles.clear() for profile in self.profiles: self.cbxProfiles.addItem(profile.name) self.cbxProfiles.setCurrentIndex(len(self.profiles) - 1) self.select_profile(len(self.profiles) - 1) self.save_current_profile_internally() if len(self.profiles) > 1: self.btnDelete.setEnabled(True) def rename_profile(self): rename_pos = [325, 490, 250, 25] if self.sender().text() == 'rename profile': self.sender().setText('OK') self.sender().move(rename_pos[0] + 220 - 55, rename_pos[1]) self.sender().resize(rename_pos[2] - 220, rename_pos[3]) self.cbxProfiles.setEnabled(False) self.btnCreate.setEnabled(False) self.btnSave.setEnabled(False) self.btnDelete.setEnabled(False) self.btnCancelRename.setVisible(True) self.txtRename.setVisible(True) current_index = self.cbxProfiles.currentIndex() self.txtRename.setText(self.profiles[current_index].name) else: self.sender().setText('rename profile') self.sender().move(rename_pos[0], rename_pos[1]) self.sender().resize(rename_pos[2], rename_pos[3]) self.cbxProfiles.setEnabled(True) self.btnCreate.setEnabled(True) self.btnSave.setEnabled(True) self.btnDelete.setEnabled(True) self.btnCancelRename.setVisible(False) self.txtRename.setVisible(False) current_index = self.cbxProfiles.currentIndex() self.profiles[current_index].name = self.txtRename.text() self.cbxProfiles.clear() for profile in self.profiles: self.cbxProfiles.addItem(profile.name) self.cbxProfiles.setCurrentIndex(current_index) self.select_profile(current_index) self.save_current_profile_internally() def cancel_rename(self): rename_pos = [325, 490, 250, 25] self.btnRename.setText('rename profile') self.btnRename.move(rename_pos[0], rename_pos[1]) self.btnRename.resize(rename_pos[2], rename_pos[3]) self.cbxProfiles.setEnabled(True) self.btnCreate.setEnabled(True) self.btnSave.setEnabled(True) self.btnDelete.setEnabled(True) self.btnCancelRename.setVisible(False) self.txtRename.setVisible(False) def check_name_validity(self, text): if text == '': self.btnRename.setEnabled(False) else: self.btnRename.setEnabled(True)