我有两个 Python 模块,每个模块都包含一个计时器。
第一个模块从操纵杆读取:
# QThrottle.py
from dataclasses import dataclass
import pygame
from PyQt5.QtCore import QObject, QTimer, pyqtSlot, pyqtSignal
@dataclass
class JData:
# 7 axes
axis = [0.0] * 7
# 16 buttons
btn = [0] * 16
class QThrottle(QObject):
# signals
axis_changed = pyqtSignal([int, float])
button_changed = pyqtSignal([int, int])
def __init__(self, parent=None):
super(QThrottle, self).__init__(parent)
# container to store last data to compare them with the new ones
self.j_data = JData()
pygame.joystick.init()
# sim_timer to run the simulator
self.timer = QTimer()
self.timer_start()
@pyqtSlot()
def on_timer_timeout(self):
# only continue if one device is plugged in
if pygame.joystick.get_count() < 1:
return
# only one joystick is foreseen here
joystick = pygame.joystick.Joystick(0)
joystick.init()
# check all axes for changes
def check_axes(id):
val = joystick.get_axis(id)
if self.j_data.axis[id] != val:
self.axis_changed.emit(id, val)
self.j_data.axis[id] = val
num_axes = range(joystick.get_numaxes())
# list(map(check_axes, num_axes))
# check all buttons for changes
def check_buttons(id):
val = joystick.get_button(i)
if self.j_data.btn[i] != val:
# print('button {id}: {val}'.format(id=i, val=val))
self.button_changed.emit(i, val)
self.j_data.btn[i] = val
num_buttons = range(joystick.get_numbuttons())
# list(map(check_buttons, num_buttons))
def timer_start(self):
self.timer.timeout.connect(self.on_timer_timeout)
# set the refresh time
self.timer.start(1)
def timer_stop(self):
self.timer.timeout.disconnect(self.on_timer_timeout)
self.timer.stop()
第二个模块目前模拟一些串行输入:
# QCom.py
from math import cos, sin, pi
from PyQt5.QtCore import QObject, QTimer, pyqtSignal, pyqtSlot
class QCom(QObject):
# signals
received_val1 = pyqtSignal(float)
received_val2 = pyqtSignal(float)
def __init__(self, parent=None):
super(QCom, self).__init__(parent)
# first variables
self.counter = 0
# sim_timer to run the simulator
self.sim_timer = QTimer()
# ##### Simulator #####
@pyqtSlot()
def on_timer_timeout(self):
self.counter += 1
self.counter %= 360
self.received_val1.emit(50 + 50 * sin(self.counter * pi / 180))
self.received_val2.emit(50 + 50 * cos(self.counter * pi / 180))
def sim_start(self):
self.sim_timer.timeout.connect(self.on_timer_timeout)
self.sim_timer.start(10)
def sim_stop(self):
self.sim_timer.timeout.disconnect(self.on_timer_timeout)
self.sim_timer.stop()
主要加载两个模块,qml-fronend 和 qml-backend:
# main.py
from PyQt5.QtCore import QThread
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtQml import QQmlApplicationEngine
from QCom import QCom
from QThrottle import QThrottle
from QmlWindow.MainWindowBackend import MainWindowBackend
if __name__ == "__main__":
import sys
# Create an instance of the application
app = QGuiApplication(sys.argv)
thread_com = QThread(app)
thread_com.start()
com = QCom()
com.moveToThread(thread_com)
com.sim_start()
thread_throttle = QThread(app)
thread_throttle.start()
throttle = QThrottle()
throttle.moveToThread(thread_throttle)
# Create QML engine
engine = QQmlApplicationEngine()
# Create a window object
backend = MainWindowBackend()
# And register it in the context of QML
engine.rootContext().setContextProperty("backend", backend)
# Load the qml file into the engine
engine.load('QmlWindow/MainWindow.qml')
com.received_val1.connect(backend.received_val1.emit)
com.received_val2.connect(backend.received_val2.emit)
engine.quit.connect(app.quit)
sys.exit(app.exec_())
QML 的东西在这里似乎并不重要。
一开始我是这样加载两个模块的:
com = QCom()
throttle = QThrottle()
而且我观察到即使在低计时器频率下我的 qml 前端也有一些抽搐的动作。尽管事实上 QCom 和 QML 之间没有通过信号连接。
我认为最好在单独的线程中运行两个模块并进行以下更改:
thread_com = QThread(app)
thread_com.start()
com = QCom()
com.moveToThread(thread_com)
com.sim_start()
thread_throttle = QThread(app)
thread_throttle.start()
throttle = QThrottle()
throttle.moveToThread(thread_throttle)
我检查了 QThread.currentThreadId()
内的 on_timer_timeout()
以查看处理程序是否在单独的线程上运行,并且确实如此。但抽搐仍然存在。在我看来,两个计时器都会干扰。但我预计如果它们都在不同的线程中运行,情况就不应该如此。
如何解决这个问题?
QML 部分:
# MainWindowBackend.py
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot
class MainWindowBackend(QObject):
def __init__(self):
QObject.__init__(self)
# Signals which shall be handled by qml
# corresponding slots are implemented in MainWindow.qml -> appWindow -> Connections
# the preferred argument name shall be 'val' to ease handling
received_val1 = pyqtSignal(float, arguments=['val'])
received_val2 = pyqtSignal(float, arguments=['val'])
// MainWindow.qml
import QtQuick 2.12
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3
ApplicationWindow {
id: appWindow
visible: true
title: "MyPanel"
property int margin: 11
width: mainLayout.implicitWidth + 2 * margin
height: mainLayout.implicitHeight + 2 * margin
minimumWidth: mainLayout.Layout.minimumWidth + 2 * margin
minimumHeight: mainLayout.Layout.minimumHeight + 2 * margin
ColumnLayout {
id: mainLayout
anchors.fill: parent
anchors.margins: appWindow.margin
RowLayout {
id: eLayout
EBox {
eId: "1"
id: e1Box
}
EBox {
eId: "2"
id: e2Box
}
}
}
// connect backend to boxes (eng 1 & 2)
// the slot on Python side: mySlot
// corresponds to qml side: onMySlot
Connections {
target: backend
onReceived_val1: { e1Box.eng = val }
onReceived_val2: { e2Box.eng = val }
}
}
// EBox.qml
import QtQuick 2.12
import QtQuick.Controls 1.4
import QtQuick.Extras 1.4
import QtQuick.Layouts 1.3
GroupBox {
property string eId: "n"
title: "Dev " + eId
// properties which are intended to be connected to the backend
property double eng: 08.15
ColumnLayout {
anchors.fill: parent
Rectangle {
Layout.fillWidth: true
Layout.minimumWidth: egauge.width
Layout.minimumHeight: egauge.height
Layout.preferredWidth: 200
Layout.preferredHeight: 100
StdGauge {
id: egauge
value: eng
label: "Unit"
title: "Title"
}
}
}
}
// StdGauge.qml
import QtQuick 2.12
import QtQuick.Controls 1.4
import QtQuick.Extras 1.4
import QtQuick.Layouts 1.3
CircularGauge {
id: cgauge
value: 0
property string label: ""
property string title: ""
anchors.centerIn: parent
Rectangle {
id: display
anchors.centerIn: parent
width: 0.4*(parent.width<parent.height?parent.width:parent.height)
height: width
color: "black"
border.color: "gray"
border.width: 3
radius: width*0.5
Text {
id: dispValue
anchors.centerIn: parent
font.pointSize: 16
font.bold: true
color: "gray"
text: cgauge.value.toFixed(2)
}
Text {
id: dispLabel
anchors.top: dispValue.bottom
anchors.horizontalCenter: dispValue.horizontalCenter
font.pointSize: 10
font.bold: true
color: "gray"
text: cgauge.label
}
}
Text {
id: dispTitle
anchors.top: display.bottom
anchors.bottom: cgauge.bottom
anchors.horizontalCenter: cgauge.horizontalCenter
font.pointSize: 16
font.bold: true
verticalAlignment: Text.AlignVCenter
color: "gray"
text: cgauge.title
}
}
附注:
我知道,那个电话
joystick = pygame.joystick.Joystick(0)
joystick.init()
in on_timer_timeout
很耗时,应该以更优雅的方式解决。但这不是这个问题的重点。