class DeusExSpawner: def __init__(self, scene, config, interval, action, locations): self.scene = scene self.config = config self.interval = interval self.action = action self.locations = locations self.deusExTypesList = [DeusExTypes.POSITIVE, DeusExTypes.NEGATIVE] self.positivePulseSound = oalOpen(self.config.sounds["nondangerZone"]) self.negativePulseSound = oalOpen(self.config.sounds["dangerZone"]) self.positiveEndingSound = oalOpen( self.config.sounds["nondangerZoneEnd"]) self.negativeEndingSound = oalOpen(self.config.sounds["dangerZoneEnd"]) self.spawnTimer = QTimer() self.spawnTimer.setTimerType(Qt.PreciseTimer) self.spawnTimer.timeout.connect(self.spawn) self.spawnTimer.setInterval(self.interval) self.spawnTimer.start() def spawn(self, isPositive=False): deusExType = None deusEx = None if isPositive: deusExType = DeusExTypes.POSITIVE deusEx = DeusEx(self.config, deusExType, self.positivePulseSound, self.positiveEndingSound) else: deusExType = DeusExTypes.NEGATIVE deusEx = DeusEx(self.config, deusExType, self.negativePulseSound, self.negativeEndingSound) deusEx.deusExActivateSignal.connect(self.action) deusEx.setZValue(2) deusEx.setPos(random.choice(self.locations)) self.scene.addItem(deusEx)
class BaseMeter(Tool): def __init__(self, engine): super().__init__(engine) self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint) self.setupUi(self) self._timer = QTimer() self._timer.setInterval(1000) self._timer.setTimerType(Qt.PreciseTimer) self._timer.timeout.connect(self.on_timer) self._timer.start() self.restore_state() self.finished.connect(self.save_state) self.chars = [] engine.signal_connect("translated", self.on_translation) def on_translation(self, old, new): for action in old: remove = len(action.text) if remove > 0: self.chars = self.chars[:-remove] self.chars += _timestamp_items(action.replace) for action in new: remove = len(action.replace) if remove > 0: self.chars = self.chars[:-remove] self.chars += _timestamp_items(action.text) def on_timer(self): raise NotImplementedError()
class VideoCapture(QWidget): def __init__(self, filename, parent): super(QWidget, self).__init__() self.cap = cv2.VideoCapture(str(filename[0])) self.length = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT)) self.frame_rate = self.cap.get(cv2.CAP_PROP_FPS) #self.codec = self.cap.get(cv2.CAP_PROP_FOURCC) self.video_frame = QLabel() parent.layout.addWidget(self.video_frame) def nextFrameSlot(self): ret, frame = self.cap.read() frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) img = QImage(frame, frame.shape[1], frame.shape[0], QImage.Format_RGB888) pix = QPixmap.fromImage(img) self.video_frame.setPixmap(pix) def start(self): self.timer = QTimer() self.timer.setTimerType(Qt.PreciseTimer) self.timer.timeout.connect(self.nextFrameSlot) self.timer.start(1000.0 / self.frame_rate) def pause(self): self.timer.stop() def deleteLater(self): self.cap.release() super(QWidget, self).deleteLater()
class MediaListPlayer(_ListPlayer): """WIP to provide most vlc.MediaPlayer functionality in the one class with the playlist. """ newframe = pyqtSignal() slider_precision = 100 def __init__(self, viewpoint_mngr, loop_mode_mngr, media_player): super().__init__( viewpoint_mngr=viewpoint_mngr, loop_mode_mngr=loop_mode_mngr, media_player=media_player, ) self.timer = QTimer() self.timer.setTimerType(Qt.CoarseTimer) self.timer.timeout.connect(self.on_timeout) self.mp.playing.connect(self.timer.start) self.mp.stopped.connect(self.timer.stop) self.mp.paused.connect(self.timer.stop) self.mediachanged.connect(self.on_mediachanged) def on_timeout(self): self.newframe.emit() @pyqtSlot(MediaItem) def on_mediachanged(self, media_item: MediaItem): media_info = media_item.info() media_fps = media_info["avg_frame_rate"] self.timer.setInterval(media_fps)
class FiringNotifier(QObject): firingSignal = pyqtSignal(int) def __init__(self, timerInterval): super().__init__() self.keys = [] # will be false when the player announces he can't shoot # the board sets this flag according to player.announceCanShoot function self.canEmit = True self.thread = QThread() self.timerInterval = timerInterval self.emitTimer = QTimer() self.emitTimer.setTimerType(Qt.PreciseTimer) self.emitTimer.timeout.connect(self.emit) self.emitTimer.setInterval(self.timerInterval) self.moveToThread(self.thread) self.thread.started.connect(self.emitTimer.start) self.thread.start() def add_key(self, key): self.keys.append(key) def remove_key(self, key): if key in self.keys: self.keys.remove(key) def emit(self): if self.canEmit: for k in self.keys: self.firingSignal.emit(k)
class Video(QObject): finish = pyqtSignal() t_changed = pyqtSignal(int) def __init__(self, clip): super(Video, self).__init__() self.clip = clip self.t = 0 self.stride = 1 / self.clip.fps def work(self): self.timer = QTimer() self.timer.setTimerType(Qt.PreciseTimer) self.timer.timeout.connect(ui.openGLWindow.update) self.timer.timeout.connect(self.updatetime) self.timer.timeout.connect(ui.audio.update) self.timer.setInterval(1000 / self.clip.fps) self.timer.start() def updatetime(self): #print(threading.current_thread()) if self.t < self.clip.duration: im = self.clip.get_frame(self.t) print(self.t) img = QImage(im.data, im.shape[1], im.shape[0], im.shape[1] * 3, QImage.Format_RGB888) q.put(img) self.setT(self.t + self.stride) else: self.stop() self.finish.emit() def pause(self): self.timer.stop() def resume(self): self.timer.start(1000 / self.clip.fps) def speed(self): self.clip = speedx.speedx(self.clip, 2) def stop(self): self.setT(0) q.queue.clear() self.timer.stop() def tiktok(self): self.clip = self.clip.fl_image(tiktok_effect) def setClip(self, clip): self.clip.close() self.clip = clip self.setT(0) self.stride = 1 / self.clip.fps def setT(self, t): self.t = t self.t_changed.emit(self.t * 10)
class LineEdit(QLineEdit): def __init__(self, parent=None): super(LineEdit, self).__init__(parent) self.text_focus = False # Clicking automatically selects all text, this allows clicks and drag # to highlight part of a url better self.clicklength = QTimer() self.clicklength.setSingleShot(True) self.clicklength.setTimerType(Qt.PreciseTimer) def mousePressEvent(self, e): if not self.text_focus: self.clicklength.start(120) self.text_focus = True else: super(LineEdit, self).mousePressEvent(e) def mouseReleaseEvent(self, e): if self.clicklength.isActive(): self.selectAll() super(LineEdit, self).mouseReleaseEvent(e) def focusOutEvent(self, e): super(LineEdit, self).focusOutEvent(e) self.text_focus = False
def playerShield(self, pw=None): pw.player.isShielded = True pw.player.shieldTimer.start() playerShieldTimer = QTimer() playerShieldTimer.setTimerType(Qt.PreciseTimer) playerShieldTimer.setInterval(15000) playerShieldTimer.timeout.connect(lambda: self.afterPlayerShield(pw)) playerShieldTimer.start() setattr(self, f"playerShieldTimer{pw.player.id}", playerShieldTimer)
def playerCantMove(self, pw): pw.firingNotifier.emitTimer.stop() pw.movementNotifier.emitTimer.stop() playerCantMoveTimer = QTimer() playerCantMoveTimer.setTimerType(Qt.PreciseTimer) playerCantMoveTimer.timeout.connect( lambda: self.afterPlayerCantMove(pw)) playerCantMoveTimer.setInterval(10000) playerCantMoveTimer.start() setattr(self, f"playerCantMoveTimer{pw.player.id}", playerCantMoveTimer)
def threaded_cooldown(func): """ A decorator that makes it so the decorate function will run in a thread, but prevents the same function from being rerun for a given time. After give time, the last call not performed will be executed. Purpose of this is to ensure writing to disc does not happen all too often, avoid IO operations reducing GUI smoothness. A drawback is that if a user "queues" a save, but reloads the file before the last save, they will load a version that is not up to date. This is not a problem for Grabber, as the settings are only read on startup. However, it's a drawback that prevents a more general use. This decorator requires being used in an instance which has a threadpool instance. """ timer = QTimer() timer.setInterval(5000) timer.setSingleShot(True) timer.setTimerType(Qt.VeryCoarseTimer) def wrapper(self, *args, **kwargs): if not hasattr(self, 'threadpool'): raise AttributeError( f'{self.__class__.__name__} instance does not have a threadpool attribute.' ) if not hasattr(self, 'force_save'): raise AttributeError( f'{self.__class__.__name__} instance does not have a force_save attribute.' ) worker = Task(func, self, *args, **kwargs) if timer.receivers(timer.timeout): timer.disconnect() if self.force_save: timer.stop() self.threadpool.start(worker) self.threadpool.waitForDone() return if timer.isActive(): timer.timeout.connect(partial(self.threadpool.start, worker)) timer.start() return timer.start() self.threadpool.start(worker) return return wrapper
class PloverWpmMeter(Tool, Ui_WpmMeter): TITLE = "WPM Meter" ROLE = "wpm_meter" _TIMEOUTS = { "wpm1": 10, "wpm2": 60, } def __init__(self, engine): super().__init__(engine) self.setupUi(self) self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint) self.wpm_method.addItem("NCRA", "ncra") self.wpm_method.addItem("Traditional", "traditional") self._timer = QTimer() self._timer.setInterval(1000) self._timer.setTimerType(Qt.PreciseTimer) self._timer.timeout.connect(self._update_wpms) self._timer.start() self._chars = [] engine.signal_connect("translated", self._on_translation) self.restore_state() self.finished.connect(self.save_state) def _on_translation(self, old, new): for action in old: remove = len(action.text) if remove > 0: self._chars = self._chars[:-remove] self._chars += _timestamp_chars(action.replace) for action in new: remove = len(action.replace) if remove > 0: self._chars = self._chars[:-remove] self._chars += _timestamp_chars(action.text) @pyqtSlot() def _update_wpms(self): max_timeout = max(self._TIMEOUTS.values()) self._chars = _filter_old_chars(self._chars, max_timeout) for name, timeout in self._TIMEOUTS.items(): chars = _filter_old_chars(self._chars, timeout) wpm = _wpm_of_chars(chars, method=self.wpm_method.currentData()) getattr(self, name).display(str(wpm))
class MovementSoundHandler: def __init__(self, config): self.config = config self.movementSignals = [] # # moving sound self.movementSound = oalOpen(self.config.sounds["tankMoving"]) self.movementSound.set_looping(True) self.movementSound.set_gain(30.0) # not moving sound self.nonMovementSound = oalOpen(self.config.sounds["tankNotMoving"]) self.nonMovementSound.set_looping(True) self.nonMovementSound.set_gain(30.0) self.soundTimer = QTimer() self.soundTimer.setTimerType(Qt.PreciseTimer) self.soundTimer.timeout.connect(self.stopMovementSound) self.soundTimer.setInterval(100) def playMovementSound(self): if self.nonMovementSound is not None and self.movementSound is not None: if self.nonMovementSound.get_state() == AL_PLAYING: self.nonMovementSound.pause() if self.movementSound.get_state() == AL_PAUSED or \ self.movementSound.get_state() == AL_STOPPED or \ self.movementSound.get_state() == AL_INITIAL: self.movementSound.play() self.soundTimer.start() def stopMovementSound(self): if self.nonMovementSound is not None and self.movementSound is not None: if self.movementSound.get_state() == AL_PLAYING: self.movementSound.pause() if self.nonMovementSound.get_state() == AL_PAUSED or \ self.nonMovementSound.get_state() == AL_STOPPED or \ self.nonMovementSound.get_state() == AL_INITIAL: self.nonMovementSound.play() self.soundTimer.stop() def activate(self): self.nonMovementSound.play() self.soundTimer.start() def deactivate(self): self.movementSound.stop() self.nonMovementSound.stop() self.soundTimer.stop()
class BaseMeter(Tool): def __init__(self, engine): super().__init__(engine) self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint) self.setupUi(self) self.is_pinned_checkbox.stateChanged.connect(self.set_is_pinned) self._timer = QTimer() self._timer.setInterval(1000) self._timer.setTimerType(Qt.PreciseTimer) self._timer.timeout.connect(self.on_timer) self._timer.start() self.restore_state() self.finished.connect(self.save_state) self.chars = [] engine.signal_connect("translated", self.on_translation) def on_translation(self, old, new): output = CaptureOutput(self.chars) output_helper = OutputHelper(output, False, False) output_helper.render(None, old, new) def on_timer(self): raise NotImplementedError() def _save_state(self, settings): settings.setValue("is_pinned", self.is_pinned_checkbox.isChecked()) def _restore_state(self, settings): is_pinned = settings.value("is_pinned", True, bool) self.is_pinned_checkbox.setChecked(is_pinned) def set_is_pinned(self): is_pinned = self.is_pinned_checkbox.isChecked() if is_pinned: self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint) else: self.setWindowFlags(self.windowFlags() & ~Qt.WindowStaysOnTopHint) self.show()
class AutoReconnectSocket(ClientSocketBase): ConnectedState = QtNetwork.QAbstractSocket.ConnectedState ConnectingState = QtNetwork.QAbstractSocket.ConnectingState UnconnectedState = QtNetwork.QAbstractSocket.UnconnectedState def __init__(self): ClientSocketBase.__init__(self) self.KeepAliveOption = True self.LowDelayOption = True self.__connection_expected = False self.qurl = QUrl() # Timer for connection attempts self.connect_timer = QTimer() self.connect_timer.setTimerType(Qt.VeryCoarseTimer) self.stateChanged.connect(self._on_state_changed) def _attempt_open(self): self.connect_timer.singleShot(0, lambda: self.open(self.qurl)) log.info(f"SOCKET OPEN ATTEMPT qurl={self.qurl}") def _on_state_changed(self, state: QtNetwork.QAbstractSocket.SocketState): log.info(f"SOCKET STATE CHANGED state={self.state_str()} qurl={self.qurl}") if state == QtNetwork.QAbstractSocket.UnconnectedState: if self.__connection_expected: self._attempt_open() elif state == QtNetwork.QAbstractSocket.ConnectedState: self.__connection_expected = True def connect(self, url): self.qurl = QUrl(url) self._attempt_open() def disconnect(self): self.__connection_expected = False self.connect_timer.stop() self.close()
class MediaPlayerCustomSignals(MediaPlayerVlclibSignals): newframe = pyqtSignal() slider_precision = 100 def __init__(self, vlc_media_player): super().__init__(vlc_media_player=vlc_media_player) self.timer = QTimer() self.timer.setTimerType(Qt.CoarseTimer) self.timer.timeout.connect(self.on_timeout) # self.playing.connect(self.timer.start) # self.stopped.connect(self.timer.stop) # self.paused.connect(self.timer.stop) # self.mediachanged.connect(self.on_mediachanged) def on_timeout(self): self.newframe.emit() def has_media(self): return True if self._vlc_obj.get_media() else False def on_mediachanged(self, e): media = self.get_media() if not media.is_parsed(): media.parse() media_fps = self._get_media_fps(media) playback_fps = media_fps * self.get_rate() self.timer.setInterval(self.slider_precision / playback_fps) def _get_media_fps(self, vlc_media) -> float: if not vlc_media: return 30 if not vlc_media.is_parsed(): vlc_media.parse() tracks = vlc_media.tracks_get() if not tracks: return 30 track = [t for t in tracks if t is not None][0] return track.video.contents.frame_rate_num
class LineEdit(QLineEdit): def __init__(self, parent=None): super(LineEdit, self).__init__(parent) self.text_focus = False self.clicklength = QTimer() self.clicklength.setSingleShot(True) self.clicklength.setTimerType(Qt.PreciseTimer) def mousePressEvent(self, e): if not self.text_focus: self.clicklength.start(120) self.text_focus = True else: super(LineEdit, self).mousePressEvent(e) def mouseReleaseEvent(self, e): if self.clicklength.isActive(): self.selectAll() super(LineEdit, self).mouseReleaseEvent(e) def focusOutEvent(self, e): super(LineEdit, self).focusOutEvent(e) self.text_focus = False
class MovementNotifier(QObject): movementSignal = pyqtSignal(int) def __init__(self, timerInterval): super().__init__() self.keys = [] self.isEmitting = False self.timerInterval = timerInterval self.thread = QThread() self.emitTimer = QTimer() self.emitTimer.setTimerType(Qt.PreciseTimer) self.emitTimer.timeout.connect(self.emit) self.moveToThread(self.thread) self.emitTimer.setInterval(self.timerInterval) self.thread.started.connect(self.emitTimer.start) self.thread.start() def add_key(self, key): self.keys.append(key) def remove_key(self, key): if key in self.keys: self.isEmitting = False self.keys.remove(key) def emit(self): keys = self.keys[:] if keys: # mainKey will be the first key that was registered # if any other key was pressed they will be omitted # with that, moving only in one direction at a time will be possible # NOTE: when holding a key (although add_key is called on the board, only one element will be # in the self.keys list) mainKey = keys[0] self.isEmitting = True self.movementSignal.emit(mainKey)
class SENSOR_TEST(QWidget): bars = None series = None chart = None labelWidgets = [] comms = None commsStatus = False deviceID = "HARP" def __init__(self): QWidget.__init__(self) # ADD GUI WIDGETS self.setupLayout() # LAUNCH GUI self.show() self.resize(800,600) def setupLayout(self): parentLayout = QVBoxLayout() parentLayout.setContentsMargins(20,20,20,20) buttonLayout = QHBoxLayout() # CREATE SERIAL COMMUNICATION CONNECT BUTTON self.connectButton = QPushButton("CONNECT") self.connectButton.setFixedSize(150, 40) self.connectButton.setCheckable(True) self.connectButton.setStyleSheet("QPushButton {background-color: #66BB6A; color: black; border-radius: 20px}" "QPushButton:hover {background-color: #4CAF50; color: black; border-radius: 20px}" "QPushButton:checked {background-color: #EF5350; color: white; border-radius: 20px}" "QPushButton:checked:hover {background-color: #F44336; color: white; border-radius: 20px}") # CREATE SENSORS CALIBRATION BUTTON self.calibrateButton = QPushButton("CALIBRATE") self.calibrateButton.setFixedSize(150, 40) self.calibrateButton.setStyleSheet("QPushButton {background-color: #E0E0E0; color: black; border-radius: 20px}" "QPushButton:hover {background-color: #BDBDBD; color: black; border-radius: 20px}" "QPushButton:pressed {background-color: #D5D5D5; color: black; border-radius: 20px}") # ADD BUTTONS TO HORIZONTAL LAYOUT buttonLayout.addWidget(self.connectButton, alignment = Qt.AlignLeft) buttonLayout.addWidget(self.calibrateButton, alignment = Qt.AlignRight) # CREATE BAR CHART TO DISPLAY SENSOR READINGS self.bars = QBarSet('Sensor Readings') self.bars.append([0 for i in range(5)]) self.chart = QChart() self.chart.setBackgroundRoundness(20) self.chart.layout().setContentsMargins(0, 0, 0, 0) self.series = QBarSeries() self.series.append(self.bars) self.chart.addSeries(self.series) self.chart.setTitle('Sensor Readings') # CREATE X-AXIS AS SENSOR LABELS xAxis = QBarCategoryAxis() labels = ["Sensor {}".format(i+1) for i in range(5)] xAxis.append(labels) # CREATE Y-AXIS AND SENSOR READINGS yAxis = QValueAxis() yAxis.setRange(-100, 100) yAxis.setTickCount(11) yAxis.setTitleText("Pressure (%)") # ADD AXES TO CHART self.chart.addAxis(xAxis, Qt.AlignBottom) self.chart.addAxis(yAxis, Qt.AlignLeft) self.chart.legend().setVisible(False) # ATTACH AXES TO SERIES self.series.attachAxis(xAxis) self.series.attachAxis(yAxis) # ADD CHART TO A VIEW chartView = QChartView(self.chart) labelLayout = QFrame() labelLayout.setStyleSheet("background-color: white; border-radius: 20px") layout1 = QVBoxLayout() labelLayout.setLayout(layout1) layout2 = QHBoxLayout() layout3 = QHBoxLayout() for i in range(5): label = QLabel("Sensor {}".format(i+1)) label.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) label.setStyleSheet("font-weight: bold;") layout2.addWidget(label) value = QLabel("0 mV") value.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) self.labelWidgets.append(value) layout3.addWidget(value) layout1.addLayout(layout2, 1) layout1.addLayout(layout3, 1) parentLayout.addLayout(buttonLayout) parentLayout.addWidget(chartView, 5) parentLayout.addWidget(labelLayout, 1) # LINK WIDGETS self.connectButton.clicked.connect(self.serialToggle) self.calibrateButton.clicked.connect(self.sensorCalibrate) self.setLayout(parentLayout) def getSensorReadings(self): """ PURPOSE Requests sensor readings from ROV and updates GUI. INPUT NONE RETURNS NONE """ # SENSOR POLLING RATE (HZ) refreshRate = 100 # START QTIMER TO REPEATEDLY UPDATE SENSORS AT THE DESIRED POLLING RATE self.timer = QTimer() self.timer.setTimerType(Qt.PreciseTimer) self.timer.timeout.connect(self.getSensorReadings) self.timer.start(int(1000*1/refreshRate)) # STOP REQUESTING SENSOR VALUES IF ROV IS DISCONNECTED if self.commsStatus == False: self.timer.stop() else: # REQEST SINGLE READING sensorReadings = self.getSensors() # UPDATE VOLTAGE READINGS self.updateVoltageReadings(sensorReadings) # SCALE SENSOR READINGS scaledReadings = self.mapPressureReadings(sensorReadings) try: # UPDATE GUI self.series.remove(self.bars) self.bars = QBarSet("") self.bars.append([float(item) for item in scaledReadings]) self.series.append(self.bars) except: self.teensyDisconnect() def mapPressureReadings(self, values): mappedValues = [] for value in values: try: mappedValues.append((200/1023)*float(value) - 100) except: pass return mappedValues def updateVoltageReadings(self, values): voltageValues = [round((3300/1023)*float(i)) for i in values] for i, label in enumerate(self.labelWidgets): label.setText(str(voltageValues[i]) + " mV") def serialToggle(self, buttonState): """ PURPOSE Determines whether to connect or disconnect from the ROV serial interface. INPUT - buttonState = the state of the button (checked or unchecked). RETURNS NONE """ # CONNECT if buttonState: self.teensyConnect() # DISCONNECT else: self.teensyDisconnect() def sensorCalibrate(self): """ PURPOSE INPUT RETURNS """ self.calibrateSensors() ######################## ### SERIAL FUNCTIONS ### ######################## def teensyConnect(self): """ PURPOSE Attempts to connect to the ROV using the comms library. Changes the appearance of the connect buttons. If connection is successful, the ROV startup procedure is initiated. INPUT NONE RETURNS NONE """ # DISABLE BUTTONS TO AVOID DOUBLE CLICKS self.connectButton.setEnabled(False) # FIND ALL AVAILABLE COM PORTS availableComPorts, comPort, identity = self.findComPorts(115200, self.deviceID) print(availableComPorts, comPort, identity) # ATTEMPT CONNECTION TO ROV COM PORT status, message = self.serialConnect(comPort, 115200) print(status, message) # IF CONNECTION IS SUCCESSFUL if status == True: # MODIFY BUTTON STYLE self.connectButton.setText('DISCONNECT') # START READING SENSOR VALUES self.getSensorReadings() # IF CONNECTION IS UNSUCCESSFUL else: self.teensyDisconnect() # RE-ENABLE CONNECT BUTTONS self.connectButton.setEnabled(True) def teensyDisconnect(self): """ PURPOSE Disconnects from the ROV using the comms library. Changes the appearance of the connect buttons INPUT NONE RETURNS NONE """ # MODIFY BUTTON STYLE self.connectButton.setText('CONNECT') self.connectButton.setChecked(False) # CLOSE COM PORT if self.commsStatus: self.comms.close() self.commsStatus = False def findComPorts(self, baudRate, identity): """ PURPOSE Find all available COM ports and requests the devices identity. INPUT - menuObject = pointer to the drop down menu to display the available COM ports. - baudRate = baud rate of the serial interface. - identity = string containing the required device identity to connect to. RETURNS - availableComPorts = list of all the available COM ports. - rovComPort = the COM port that belongs to the device. - identity = the devices response from an identity request. """ # CREATE LIST OF ALL POSSIBLE COM PORTS ports = ['COM%s' % (i + 1) for i in range(256)] deviceIdentity = "" comPort = None availableComPorts = [] # CHECK WHICH COM PORTS ARE AVAILABLE for port in ports: try: comms = serial.Serial(port, baudRate, timeout = 1) availableComPorts.append(port) # REQUEST IDENTITY FROM COM PORT self.commsStatus = True deviceIdentity = self.getIdentity(comms, identity) comms.close() self.commsStatus = False # FIND WHICH COM PORT IS THE ROV if deviceIdentity == identity: comPort = port break # SKIP COM PORT IF UNAVAILABLE except (OSError, serial.SerialException): pass return availableComPorts, comPort, deviceIdentity def getIdentity(self, serialInterface, identity): """ PURPOSE Request identity from a defined COM port. INPUT - serialInterface = pointer to the serial interface object. - identity = the desired identity response from the device connected to the COM port. RETURNS - identity = the devices response. """ identity = "" startTime = datetime.now() elapsedTime = 0 # REPEATIDELY REQUEST IDENTIFICATION FROM DEVICE FOR UP TO 3 SECONDS while (identity == "") and (elapsedTime < 3): self.serialSend("?I", serialInterface) identity = self.serialReceive(serialInterface) elapsedTime = (datetime.now() - startTime).total_seconds() return identity def serialConnect(self, comPort, baudRate): """ PURPOSE Attempts to initialise a serial communication interface with a desired COM port. INPUT - rovComPort = the COM port of the ROV. - baudRate = the baud rate of the serial interface. RETURNS NONE """ self.commsStatus = False if comPort != None: try: self.comms = serial.Serial(comPort, baudRate, timeout = 1) message = "Connection to ROV successful." self.commsStatus = True except: message = "Failed to connect to {}.".format(comPort) else: message = "Failed to recognise device identity." return self.commsStatus, message def serialSend(self, command, serialInterface): """ PURPOSE Sends a string down the serial interface to the ROV. INPUT - command = the command to send. - serialInterface = pointer to the serial interface object. RETURNS NONE """ if self.commsStatus: try: serialInterface.write((command + '\n').encode('ascii')) except: self.teensyDisconnect() print("Failed to send command.") def serialReceive(self, serialInterface): """ PURPOSE Waits for data until a newline character is received. INPUT - serialInterface = pointer to the serial interface object. RETURNS NONE """ received = "" try: received = serialInterface.readline().decode('ascii').strip() except: self.teensyDisconnect() print("Failed to receive data.") return(received) def getSensors(self): """ PURPOSE Send request to device to return sensor readings. INPUT NONE RETURNS - results = array containing the sensor readings. """ results = [] # REQUEST SENSOR READINGS command = "?S" self.serialSend(command, self.comms) # READ RESPONSE INTO AN ARRAY results = self.serialReceive(self.comms).split(",") print(results) return results def calibrateSensors(self): """ """ # REQUEST SENSOR READINGS command = "?C" self.serialSend(command, self.comms)
class ServiceManager(QThread): start_job_signal = pyqtSignal(object) abort_running_job_signal = pyqtSignal() force_psd_creation_signal = pyqtSignal() job_widget_signal = pyqtSignal(object) tcp_respond_signal = pyqtSignal(object) # LED signals response_led_start = pyqtSignal() response_led_stop = pyqtSignal() request_led_signal = pyqtSignal() # Green LED alive timer, signal the user we are alive alive_led_signal = pyqtSignal() job_active = False pickle_cache = b'' transfer_cache = b'' def __init__(self, control_app, app, ui, logging_queue): super(ServiceManager, self).__init__() # global LOGGER # LOGGER = setup_queued_logger(__name__, logging_queue) self.control_app, self.app, self.ui = control_app, app, ui self.job_working_queue = list() self.job_queue = list() self.empty_job = Job(_('Kein Job'), '', get_user_directory(), 'mayaSoftware') self.current_job = self.empty_job # Control app signals self.start_job_signal.connect(self.control_app.add_render_job) self.job_widget_signal.connect(self.control_app.update_job_widget) self.abort_running_job_signal.connect( self.control_app.abort_running_job) self.force_psd_creation_signal.connect( self.control_app.watcher_force_psd_creation) # Run service manager socket server self.server = None self.address = ('', 0) self.hostname = '' # Timer's must be created inside thread event loop, I guess... self.alive_led_timer = None self.validate_queue_timer = None def response_start_led(self): self.response_led_start.emit() def response_stop_led(self): self.response_led_stop.emit() def alive_led_blink(self): self.alive_led_signal.emit() def run(self): # Run service manager socket server self.address = (get_valid_network_address(), SocketAddress.service_port) sig_dest = (self.receive_server_msg, self.response_start_led, self.response_stop_led) self.server = rsm_server(sig_dest, self.address) # Setup queue validation self.validate_queue_timer = QTimer() self.validate_queue_timer.setTimerType(Qt.VeryCoarseTimer) self.validate_queue_timer.setInterval(300000) self.validate_queue_timer.timeout.connect(self.validate_queue) self.validate_queue_timer.start() # Connect Service Manager Server LED's self.response_led_start.connect(self.control_app.led_socket_recv_start) self.response_led_stop.connect(self.control_app.led_socket_recv_end) self.response_led_stop.connect(self.control_app.led_socket_send_end) self.request_led_signal.connect(self.control_app.led_socket_send_start) self.alive_led_signal.connect(self.control_app.alive_led_blink) # Setup alive LED self.alive_led_timer = QTimer() self.alive_led_timer.setInterval(1500) self.alive_led_timer.timeout.connect(self.alive_led_blink) self.alive_led_timer.start() try: self.hostname = socket.gethostbyaddr(self.address[0])[0] except Exception as e: LOGGER.error('%s', e) LOGGER.info('Service manager available at %s - %s', self.address[0], self.hostname) # Clean local work directory try: MoveJobSceneFile.clear_local_work_dir() except Exception as e: LOGGER.error('Error cleaning local work directory: %s', e) LOGGER.info('Service manager cleaned up local work directory.') # Run thread event loop self.exec() LOGGER.info( 'Service manager received exit signal and is shutting down.') self.server.shutdown() self.server.server_close() LOGGER.info('Service manager Socket server shut down.') def validate_queue(self): """ Test if job items have expired """ for job in self.job_queue: if job.status < JobStatus.finished: # Skip unfinished or queued jobs continue created = datetime.fromtimestamp(job.created) if (datetime.now() - created) > timedelta(hours=24): self._clear_local_job_file(job) self.job_queue.remove(job) # Update Remote Index for idx, job in enumerate(self.job_queue): job.remote_index = idx def prepare_queue_transfer(self): """ Transfer the job queue to the client as serialized json dictonary """ if not self.transfer_cache: # Create serialized queue byte encoded serialized_queue = self.serialize_queue(self.job_queue) serialized_queue = serialized_queue.encode(encoding='utf-8') # Cache the result self.cache_transfer_queue(serialized_queue) else: serialized_queue = self.transfer_cache response = serialized_queue + JOB_DATA_EOS if self.is_queue_finished(): # All Jobs finished, tell clients to stop query's response = serialized_queue + b'Queue-Finished' + JOB_DATA_EOS LOGGER.debug( 'Service Manager adding Queue finished data to job transfer queue.' ) return response @staticmethod def serialize_queue(queue): """ Return the queued Job class instances as serialized json dictonary """ job_dict = dict() for idx, job in enumerate(queue): # Update Remote Index job.remote_index = idx # Update Job object job_dict.update({idx: job.__dict__}) return json.dumps(job_dict) def cache_transfer_queue(self, serialized_queue): LOGGER.debug('Caching serialized job data queue in transfer cache.') self.transfer_cache = serialized_queue def invalidate_transfer_cache(self): if self.transfer_cache: LOGGER.debug('Invalidating transfer cache.') self.transfer_cache = b'' def start_job_file_transfer(self, job: Job): LOGGER.debug('Starting Job File Transfer for %s', job.title) file_transfer = JobFileTransfer(self, self._file_transfer_finished, job) file_transfer.start() @pyqtSlot(Job) def _file_transfer_finished(self, job: Job): self.replace_job_in_queue(job, self.job_queue) self.job_working_queue.append(job) LOGGER.debug('Finished Job File Transfer for %s', job.title) self.start_job() def job_finished(self): """ Called from app if last job finished """ self.job_active = False self.start_job() def start_job(self): """ Start the next job in the queue if no job is running """ if not self.job_working_queue: return if not self.job_active: self.current_job = self.job_working_queue.pop(0) self.current_job.render_dir = create_unique_render_path( self.current_job.file, self.current_job.render_dir) self.start_job_signal.emit(copy_job(self.current_job)) self.job_active = True def add_job(self, job_data, client: str = None): if type(job_data) is str: # Remove trailing semicolon if job_data.endswith(';'): job_data = job_data[:-1] # Convert to tuple job_data = tuple(job_data.split(';')) if not len(job_data) > 2: return False try: job_item = Job(*job_data) except Exception as e: LOGGER.error('Error creating job from socket stream: %s %s', job_data, e) return False if not job_item.file or not job_item.render_dir: return False if client: job_item.client = client if not os.path.exists(job_item.file): return False if not os.path.exists(job_item.render_dir): return False self.invalidate_transfer_cache() job_item.remote_index = len(self.job_queue) self.job_queue.append(job_item) self.job_widget_signal.emit(job_item) self.start_job_file_transfer(job_item) return True def cancel_job(self, job): if job.in_progress: LOGGER.info('Aborting currently running Job.') self.abort_running_job_signal.emit() job.set_canceled() # Remove from working queue if job in self.job_working_queue: self.job_working_queue.remove(job) self.invalidate_transfer_cache() def move_job(self, job, to_top=True): self.update_queue_order(job, self.job_working_queue, to_top) new_idx = self.update_queue_order(job, self.job_queue, to_top) if new_idx: job.remote_index = new_idx # Find job in progress and move it to top job_in_progress = None for __j in self.job_queue: if __j.in_progress: job_in_progress = __j break if job_in_progress: self.update_queue_order(job_in_progress, self.job_queue, True) self.update_control_app_job_widget() self.invalidate_transfer_cache() def update_control_app_job_widget(self): # Clear job widget self.job_widget_signal.emit(None) # Re-create job manager widget for __j in self.job_queue: self.job_widget_signal.emit(__j) def set_job_failed(self): self.current_job.set_failed() self._clear_local_job_file(self.current_job) self.invalidate_transfer_cache() def set_job_canceled(self): self.current_job.set_canceled() self._clear_local_job_file(self.current_job) self.invalidate_transfer_cache() def set_job_finished(self): self.current_job.set_finished() self._clear_local_job_file(self.current_job) self.invalidate_transfer_cache() def set_job_status(self, status: int): self.current_job.status = status self.invalidate_transfer_cache() def set_job_status_name(self, status_name): self.current_job.status_name = status_name self.invalidate_transfer_cache() def set_job_img_num(self, img_num: int = 0, total_img_num: int = 0): if total_img_num: self.current_job.total_img_num = total_img_num if img_num: self.current_job.img_num = img_num self.invalidate_transfer_cache() @staticmethod def replace_job_in_queue(job_item, job_queue) -> bool: if job_item.remote_index > len(job_queue): return False job_queue[job_item.remote_index] = job_item return True @staticmethod def update_queue_order(job_item, list_queue, to_top): new_idx = None if to_top: insert_idx = 0 else: insert_idx = len(list_queue) if job_item in list_queue: list_queue.remove(job_item) list_queue.insert(insert_idx, job_item) new_idx = list_queue.index(job_item) return new_idx @staticmethod def _clear_local_job_file(job): if not job.scene_file_is_local: return MoveJobSceneFile.delete_local_scene_files(job.file) def is_queue_finished(self): """ Return True if all jobs in the queue are finished """ finished = False for job in self.job_queue: if job.status < JobStatus.finished: break else: # No unfinished jobs in the queue, mark queue finished if len(self.job_queue): # Only mark finished if there are actually jobs in the queue finished = True return finished def is_file_in_transfer(self): for job in self.job_queue: if job.status == JobStatus.file_transfer: return True return False def get_job_from_index(self, idx): job = None if len(self.job_queue) > idx: job = self.job_queue[idx] return job def receive_server_msg(self, msg, client_name=None, tcp_handler=None): """ Receive client requests from socket server and respond accordingly """ self.request_led_signal.emit() if client_name: if msg != 'GET_JOB_DATA': LOGGER.debug('Service Manager received: "%s" from client %s', msg, client_name) else: LOGGER.debug('Service manager received: %s', msg) response = 'Unknown command or Job you referred to is no longer in the queue.' # ----------- CLIENT CONNECTED ------------ if msg.startswith('GREETING'): try: version = int(msg[-1:]) except ValueError: version = 0 if version and version > 2: LOGGER.info('Client with version %s connected.', version) response = _('Render Dienst verfuegbar @ {} ' 'Maya Version: {}').format( self.hostname, self.ui.comboBox_version.currentText()) else: LOGGER.info('Invalid Client version connected!') response = _( 'Render Dienst verfuegbar @ {}<br>' '<span style="color:red;">Die Client Version wird nicht unterstuetzt! ' 'Client Aktualisierung erforderlich!</span>').format( self.hostname) # ----------- TRANSFER RENDERER ------------ elif msg == 'GET_RENDERER': response = 'RENDERER ' # Convert list to ; separated string for __r in AVAILABLE_RENDERER: response += __r + ';' # Remove trailing semicolon response = response[:-1] # ----------- ADD REMOTE JOB ------------ elif msg.startswith('ADD_JOB'): job_string_data = msg[len('ADD_JOB '):] msg_job_idx = len(self.job_queue) result = self.add_job(job_string_data, client_name) if result: response = _('Job #{0:02d} eingereiht in laufende Jobs.' ).format(msg_job_idx) else: response = _( '<b>Job abgelehnt! </b><span style="color:red;">' 'Die Szenendatei oder das Ausgabeverzeichnis sind für den Server nicht verfügbar!</span>' ) # ----------- SEND JOB STATUS MESSAGE ------------ elif msg == 'GET_STATUS': response = _('Momentan im Rendervorgang: ' '{0} - {1:03d} / {2:03d} Layer erzeugt.<br/>' '{3:02d} Jobs in der Warteschlange.').format( self.control_app.current_job.title, self.control_app.current_job.img_num, self.control_app.current_job.total_img_num, len(self.job_working_queue)) # ----------- TRANSFER JOB QUEUE ------------ elif msg == 'GET_JOB_DATA': # Send the queue as serialized JSON response = self.prepare_queue_transfer() # ----------- MOVE JOB ------------ elif msg.startswith('MOVE_JOB'): job_index, job, to_top = None, None, False if msg.startswith('MOVE_JOB_TOP'): job_index = msg[len('MOVE_JOB_TOP '):] to_top = True elif msg.startswith('MOVE_JOB_BACK'): job_index = msg[len('MOVE_JOB_BACK '):] to_top = False if job_index: job = self.get_job_from_index(int(job_index)) if job and not self.is_file_in_transfer(): try: self.move_job(job, to_top) response = _('{} in Warteschlange bewegt.').format( job.title) except Exception as e: LOGGER.error(e) response = _('Job mit index {} konnte nicht bewegt werden.' ).format(job_index) else: response = _( 'Jobs können nicht bewegt werden während laufenden Dateitransfers.' ) # ----------- CANCEL JOB ------------ elif msg.startswith('CANCEL_JOB'): job_index = msg[len('CANCEL_JOB '):] job = self.get_job_from_index(int(job_index)) if job: self.cancel_job(job) response = _('{} wird abgebrochen.').format(job.title) else: response = _( 'Job mit index {} konnte nicht abgebrochen werden.' ).format(job_index) # ----------- FORCE PSD REQUEST ------------ elif msg.startswith('FORCE_PSD_CREATION'): job_index = msg[len('FORCE_PSD_CREATION '):] job = self.get_job_from_index(int(job_index)) if job: if job is self.current_job: response = _( 'PSD Erstellung fuer Job {} wird erzwungen.').format( job.title) self.force_psd_creation_signal.emit() else: response = _( 'Kann PSD Erstellung fuer Job {} nicht erzwingen.' ).format(job.title) if tcp_handler: self.tcp_respond_signal.connect(tcp_handler.respond) if type(response) is str: LOGGER.debug('Sending response: %s', response) else: LOGGER.debug('Sending response: transfer cache - %s', len(response)) self.tcp_respond_signal.emit(response)
class Player(QGraphicsObject): canShootSignal = pyqtSignal(CanPlayerShootSignalData) def __init__(self, playerDetails, config, color, firingKey, movementKeys, playerLevels, field, targetType, animationTimer, bulletTimer, killEmitter, playerDeadEmitter, gameOverEmitter): super().__init__() # only some of the properties from playerDetails are used # that are needed for logic of the player self.id = playerDetails.id self.points = playerDetails.points if playerDetails.lives is None: self.lives = 2 else: self.lives = playerDetails.lives if playerDetails.level is None: self.level = 1 else: self.level = playerDetails.level self.isAlive = playerDetails.isAlive self.config = config self.color = color self.isShielded = False self.firingKey = firingKey self.movementKeys = movementKeys self.playerLevels = playerLevels self.field = field self.targetType = targetType self.animationTimer = animationTimer self.bulletTimer = bulletTimer self.killEmitter = killEmitter self.playerDeadEmitter = playerDeadEmitter self.gameOverEmitter = gameOverEmitter self.startingPos = None # used to determine if the player can shoot self.firedBullets = 0 self.rateOfFire = self.playerLevels[f"star{self.level}"]["rateOfFire"] self.bulletSpeed = self.playerLevels[f"star{self.level}"]["bulletSpeed"] # initial cannon direction self.canonDirection = Direction.UP self.__init_ui__() # texture animation self.textureTimer = animationTimer self.textureTimer.timeout.connect(self.updateUi) # sounds #self.shotSound = QSound(self.config.sounds["playerShot"]) self.shotSound = oalOpen(self.config.sounds["playerShot"]) def __init_ui__(self): # set up player textures, refresh rate and transformation origin point self.textures = [] self.textures.append(QImage(f"Resources/Images/Tanks/{self.color}/{self.color}FP.v{self.level}.png")) self.textures.append(QImage(f"Resources/Images/Tanks/{self.color}/{self.color}SP.v{self.level}.png")) self.currentTexture = 0 self.height = self.textures[0].height() self.width = self.textures[0].width() self.shieldTextures = [] self.shieldTextures.append(QImage(f"Resources/Images/Shield/shield{self.width}FP.png")) self.shieldTextures.append(QImage(f"Resources/Images/Shield/shield{self.width}SP.png")) self.currentShieldTexture = 0 self.shieldTimer = QTimer() self.shieldTimer.setTimerType(Qt.PreciseTimer) self.shieldTimer.setInterval(50) self.shieldTimer.timeout.connect(self.shieldUi) self.m_boundingRect = QRectF(0, 0, self.width, self.height) # setting transform origin point to center of the player so the rotation will be in regard to the center self.setTransformOriginPoint(QPoint(self.boundingRect().width() / 2, self.boundingRect().height() / 2)) # override default bounding rect def boundingRect(self): return self.m_boundingRect # override default paint def paint(self, QPainter, QStyleOptionGraphicsItem, QWidget): QPainter.drawImage(0, 0, self.textures[self.currentTexture]) if self.isShielded: QPainter.drawImage(0, 0, self.shieldTextures[self.currentShieldTexture]) def updateUi(self): self.currentTexture = 1 if self.currentTexture == 0 else 0 self.update() def shieldUi(self): self.currentShieldTexture = 1 if self.currentShieldTexture == 0 else 0 self.update() # movements def moveRight(self): self.setX(self.x() + 1) def moveLeft(self): self.setX(self.x() - 1) def moveDown(self): self.setY(self.y() + 1) def moveUp(self): self.setY(self.y() - 1) # rotations, absolute degrees def rotate(self, direction): if direction == Direction.RIGHT: self.setRotation(90) elif direction == Direction.LEFT: self.setRotation(-90) elif direction == Direction.DOWN: self.setRotation(180) elif direction == Direction.UP: self.setRotation(0) def canMove(self, direction): canMove = True allObjects = self.scene().items() x1 = self.x() y1 = self.y() if direction == Direction.RIGHT: x1 += 1 elif direction == Direction.LEFT: x1 -= 1 elif direction == Direction.DOWN: y1 += 1 elif direction == Direction.UP: y1 -= 1 x2 = x1 + self.width y2 = y1 + self.height for obj in allObjects: # don't camper to self and field oType = type(obj) if self != obj and oType != QGraphicsRectItem: if type(obj) == Block: # omit bushes and ice if obj.type == BlockType.bush or obj.type == BlockType.ice: continue if type(obj) == DeusEx: continue objParent = obj.parentItem() objX1 = 0 objY1 = 0 if objParent is None: objX1 = obj.x() objY1 = obj.y() else: objSceneCoords = obj.mapToScene(obj.pos()) objX1 = objSceneCoords.x() objY1 = objSceneCoords.y() objX2 = objX1 + obj.boundingRect().width() objY2 = objY1 + obj.boundingRect().height() if x1 < objX2 and x2 > objX1 and y1 < objY2 and y2 > objY1: canMove = False break return canMove # NOTE: on 'Right' and 'Down' you will notice that there is a magic '-1' # it's there because there is some reason the bounding rect of the field will be # one pixel larger than the defined size def updatePosition(self, key): # before each move check if move is possible if key == self.movementKeys["Right"]: # check if the element is on the edge of the field if self.pos().x() + self.width < self.field.x() + self.field.boundingRect().width() - 1: if self.canMove(Direction.RIGHT): # else move in the desired direction self.moveRight() # check if the canon is facing the desired direction # if not, rotate to it and set the direction if not self.canonDirection == Direction.RIGHT: self.rotate(Direction.RIGHT) self.canonDirection = Direction.RIGHT elif key == self.movementKeys["Left"]: if self.pos().x() > self.field.x(): if self.canMove(Direction.LEFT): self.moveLeft() if not self.canonDirection == Direction.LEFT: self.rotate(Direction.LEFT) self.canonDirection = Direction.LEFT elif key == self.movementKeys["Down"]: if self.pos().y() + self.height < self.field.y() + self.field.boundingRect().height() - 1: if self.canMove(Direction.DOWN): self.moveDown() if not self.canonDirection == Direction.DOWN: self.rotate(Direction.DOWN) self.canonDirection = Direction.DOWN elif key == self.movementKeys["Up"]: if self.pos().y() > self.field.y(): if self.canMove(Direction.UP): self.moveUp() if not self.canonDirection == Direction.UP: self.rotate(Direction.UP) self.canonDirection = Direction.UP def shoot(self, key): if self.firedBullets < self.rateOfFire: if key == self.firingKey: # create the bullet bullet = Bullet(self.canonDirection, self) # announce that the player can't shoot until the bullet calls this function again self.announceCanShoot(False) # set the bullet in the center of the tank # 0.37 is the 37% aspect ration of the width/height of the tank so the bullet # (with its width/height will be in the middle) # magic numbers are based on the size of the image itself and the black margin # between the image end and the tank object itself in that image if self.canonDirection == Direction.UP: bullet.setPos(self.x() + self.boundingRect().width() * 0.37, self.y() - 15) elif self.canonDirection == Direction.DOWN: bullet.setPos(self.x() + self.boundingRect().width() * 0.37, self.y() + self.boundingRect().height() + 5) elif self.canonDirection == Direction.LEFT: bullet.setPos(self.x() - 15, self.y() + self.boundingRect().height() * 0.37) elif self.canonDirection == Direction.RIGHT: bullet.setPos(self.x() + self.boundingRect().width() + 5, self.y() + self.boundingRect().height() * 0.37) # add the bullet to the scene self.scene().addItem(bullet) self.shotSound.play() def announceCanShoot(self, canShoot): # if canShoot is true, that means the bullet is destroyed, decrease fired bullets number and emit it can shoot # if canShoot is false, that means the the player fired a bullet, but don't emit the player can shoot # immediately but first check if fired bullets reached rate of fire, if reached, emit player cannot shoot # else, don't emit anything (which means that the firing notifier will still have canEmit flag set to True) if canShoot: self.firedBullets -= 1 self.canShootSignal.emit(CanPlayerShootSignalData(self.id, canShoot)) else: self.firedBullets += 1 if self.firedBullets == self.rateOfFire: self.canShootSignal.emit(CanPlayerShootSignalData(self.id, canShoot)) # will be used for DeusEx and will be wrapped in player wrapper def levelUp(self): if not self.level == 4: self.level += 1 self.rateOfFire = self.playerLevels[f"star{self.level}"]["rateOfFire"] self.bulletSpeed = self.playerLevels[f"star{self.level}"]["bulletSpeed"] self.updateTextures() def levelDown(self): if self.level != 1: self.level -= 1 self.rateOfFire = self.playerLevels[f"star{self.level}"]["rateOfFire"] self.bulletSpeed = self.playerLevels[f"star{self.level}"]["bulletSpeed"] self.updateTextures() def resetPlayer(self): self.lives -= 1 if self.lives < 0: self.playerDeadEmitter.playerDeadSignal.emit(self.id) return self.level = 1 self.rateOfFire = self.playerLevels[f"star{self.level}"]["rateOfFire"] self.bulletSpeed = self.playerLevels[f"star{self.level}"]["bulletSpeed"] self.updateTextures() self.setPos(self.startingPos) def updateTextures(self): self.__init_ui__()
class Danmaku(QWidget): onModified = pyqtSignal(str) def __init__(self, word, paraphrase, y, show_paraphrase=None, color=None): super().__init__() self._word = word self._paraphrase = paraphrase self._stop_move = False self._show_detail = False self.modified = False self._show_paraphrase = show_paraphrase \ if show_paraphrase is not None else \ utils.get_setting("danmaku_default_show_paraphrase") self._color = color if color is not None else \ utils.get_setting("danmaku_default_color") self._cleared = False self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint | Qt.ToolTip | Qt.X11BypassWindowManagerHint # for gnome ) self.setAttribute(Qt.WA_TranslucentBackground, True) self.setAttribute(Qt.WA_DeleteOnClose) self.initUI() self.initPosition(y) # self.installEventFilter(self) # def eventFilter(self, o, e): # # due to flag `X11BypassWindowManagerHint`, event `WindowDeactivate` doesn't work # if e.type() == QEvent.WindowDeactivate: # self._show_detail = False # self._stop_move = False # self.setWindowOpacity(0.5) # self._continenter.hide() # return False # return super().eventFilter(o, e) @property def show_paraphrase(self): return self._show_paraphrase @show_paraphrase.setter def show_paraphrase(self, value): self._show_paraphrase = value self.modified = True self.onModified.emit('show_paraphrase') @property def color(self): return self._color @color.setter def color(self, value): self._color = value self.modified = True self.onModified.emit('color') @property def cleared(self): return self._cleared @cleared.setter def cleared(self, value): self._cleared = value self.modified = True self.onModified.emit('cleared') def setWordQss(self): bg_color, font_color = utils.COLORS[self.color] self._word_label.setStyleSheet( f"QLabel{{background-color:rgb({bg_color}); color:rgb({font_color}); padding:5; border-radius:6px}}" ) def initUI(self): self.setWindowOpacity(utils.get_setting("danmaku_transparency")) word = WordLabel(self._word) if self.show_paraphrase: word.setText(word.text() + " " + self._paraphrase.splitlines()[0]) word.onEnter.connect(self.enterWordEvent) word.onLeave.connect(self.leaveWordEvent) word.onMousePress.connect(self.mousePressWordEvent) word.onMouseMove.connect(self.mouseMoveWordEvent) word.onMouseRelease.connect(self.mouseReleaseWordEvent) self._word_label = word head = QHBoxLayout() head.addWidget(word) head.addStretch(1) word.setFont(QFont("Consolas", 18)) body = QVBoxLayout() body.addLayout(head) self.setLayout(body) self.setWordQss() continenter = QWidget(self) continenter.setObjectName("continenter") continenter.setStyleSheet( "#continenter{background-color:white; padding:5; border-radius:6px}" ) continenter.hide() self._continenter = continenter body.addStretch(1) body.addWidget(continenter) detail = QVBoxLayout() continenter.setLayout(detail) paraphrase = QLabel(self._paraphrase) paraphrase.setFont(QFont("Consolas", 15)) paraphrase.setStyleSheet("QLabel{color:black;}") paraphrase.setWordWrap(True) paraphrase.setMaximumWidth(300) detail.addWidget(paraphrase) rbtns = QHBoxLayout() for name, (color, _) in utils.COLORS.items(): rbtn = QRadioButton(None) qss = f""" QRadioButton::indicator {{ width:13px; height:13px; background-color:rgb({color}); border: 2px solid rgb({color}); }} QRadioButton::indicator:checked {{ border: 2px solid black; }} """ rbtn.setStyleSheet(qss) rbtn.setChecked(name == self.color) rbtn.toggled.connect(self.clickColor) rbtn.color = name rbtns.addWidget(rbtn) detail.addLayout(rbtns) btns = QHBoxLayout() clear = QPushButton("Clear") clear.setStyleSheet("QPushButton{color:black;}") clear.clicked.connect(self.clickClear) btns.addWidget(clear) switch_paraphrase = QPushButton( "Hide Paraphrase" if self.show_paraphrase else "Show Paraphrase") switch_paraphrase.setStyleSheet("QPushButton{color:black;}") switch_paraphrase.clicked.connect(self.clickSwitch) btns.addWidget(switch_paraphrase) self._switch_paraphrase = switch_paraphrase detail.addLayout(btns) self._clear = clear self.show() def clickColor(self, e): if e: sender = self.sender() self.color = sender.color self.setWordQss() def clickClear(self): self.cleared = not self.cleared self._clear.setText("Redo" if self.cleared else "Clear") def clickSwitch(self): self.show_paraphrase = not self.show_paraphrase if self.show_paraphrase: self._word_label.setText(self._word + " " + self._paraphrase.splitlines()[0]) else: self._word_label.setText(self._word) self._switch_paraphrase.setText( "Hide Paraphrase" if self.show_paraphrase else "Show Paraphrase") def enterWordEvent(self): self.setWindowOpacity(1) def leaveWordEvent(self): if not self._show_detail: self.setWindowOpacity(utils.get_setting("danmaku_transparency")) def mousePressWordEvent(self, e): if e.button() == Qt.LeftButton: self._press_point = e.globalPos() - self.pos() self._press_start = e.globalPos() e.accept() def mouseMoveWordEvent(self, e): if e.buttons() & Qt.LeftButton: self.move(e.globalPos() - self._press_point) e.accept() def mouseReleaseWordEvent(self, e): if (e.globalPos() - self._press_start).manhattanLength() < 10: self._show_detail = not self._show_detail if self._show_detail: self._stop_move = True self._continenter.show() else: self._stop_move = False self._continenter.hide() self.adjustSize() def initPosition(self, y): self._timer = QTimer(self) self._timer.setTimerType(Qt.PreciseTimer) self._timer.timeout.connect(self.update) speed = utils.get_setting("danmaku_speed") delta = 1 while round(delta / speed) < 17: delta += 1 self._timer.start(round(delta / speed)) self._delta = delta w = QDesktopWidget().availableGeometry().width() self.move(w, y) def update(self): if self._stop_move: return x = self.x() - self._delta self.move(x, self.y()) if x < -self.width(): self._timer.stop() self.close()
class Video(QObject): finish = pyqtSignal() t_changed = pyqtSignal(int) # 初始化视频信息 def __init__(self, clip): super(Video, self).__init__() self.clip = clip self.t = 0 self.fps = self.clip.fps if self.clip else 30 self.stride = 1 / self.fps self.duration = self.clip.duration if self.clip else ui.audio_backend.duration # 设置定时器,定时画视频帧并提醒音频对象同步读取 def work(self): self.timer = QTimer() self.timer.setTimerType(Qt.PreciseTimer) if self.clip: self.timer.timeout.connect(ui.openGLWidget.update) self.timer.timeout.connect(self.updatetime) self.timer.timeout.connect(ui.audio.update) self.timer.setInterval(1000 * self.stride) self.timer.start() # 根据进度入队新视频数据,若超时长则结束 def updatetime(self): if self.t < self.duration: if self.clip: im = self.clip.get_frame(self.t) img = QImage(im.data, im.shape[1], im.shape[0], im.shape[1] * 3, QImage.Format_RGB888) q.put(img) self.setT(self.t + self.stride) else: self.stop() self.finish.emit() def pause(self): self.timer.stop() self.t -= (2 * self.stride) def resume(self): self.timer.start(1000 * self.stride) def stop(self): self.timer.stop() self.setT(0) # 某一片段的抖音效果 def tiktok(self): if isinstance(self.clip, VideoFileClip): self.clip = self.clip.fl_image(tiktok_effect) ui.cur_listWidget.currentItem().setClip(self.clip, self.clip.audio) ui.openGLWidget.update() # 设置要播放的新视频 def setClip(self, clip): self.clip = clip self.setT(0) self.fps = self.clip.fps if self.clip else 30 self.stride = 1 / self.fps self.duration = self.clip.duration if self.clip else ui.audio_backend.duration self.timer = QTimer() self.timer.setTimerType(Qt.PreciseTimer) if self.clip: self.timer.timeout.connect(ui.openGLWidget.update) self.timer.timeout.connect(self.updatetime) self.timer.timeout.connect(ui.audio.update) self.timer.setInterval(1000 * self.stride) # 调整播放进度 def setT(self, t): self.t = t self.t_changed.emit(self.t * 10)
class DeusEx(QGraphicsObject): deusExActivateSignal = pyqtSignal(DeusExSignalData) def __init__(self, config, type, pulseSound, endingSound): super().__init__() self.type = type self.config = config self.width = 200 self.height = 200 self.m_boundingRect = QRectF(0, 0, self.width, self.height) self.m_painterPath = QPainterPath() self.m_painterPath.addEllipse(self.m_boundingRect) # radial gradient settings self.rgcx = self.m_boundingRect.center().x() self.rgcy = self.m_boundingRect.center().y() self.rgMinRadius = 50 self.rgMaxRadius = 300 self.rgCurrentRadius = 50 self.rgfx = self.rgcx self.rgfy = self.rgcy self.rg = QRadialGradient(self.rgcx, self.rgcy, self.rgCurrentRadius, self.rgfx, self.rgfy) if self.type is DeusExTypes.POSITIVE: firstClr = QColor(Qt.green) firstClr.setAlphaF(0.7) secondClr = QColor(Qt.darkGreen) secondClr.setAlphaF(0.7) self.rg.setColorAt(0.0, firstClr) self.rg.setColorAt(1.0, secondClr) else: firstClr = QColor(Qt.red) firstClr.setAlphaF(0.7) secondClr = QColor(Qt.darkRed) secondClr.setAlphaF(0.7) self.rg.setColorAt(0.0, firstClr) self.rg.setColorAt(1.0, secondClr) # pulsing sound self.pulseSound = pulseSound self.endingSound = endingSound # pulsing timer self.pulseTimer = QTimer() self.pulseTimer.setTimerType(Qt.PreciseTimer) self.pulseTimer.timeout.connect(self.pulse) self.pulseTimer.start(100) # pre activate timer self.preActivateTimer = QTimer() self.preActivateTimer.setTimerType(Qt.PreciseTimer) self.preActivateTimer.timeout.connect(self.preActivate) if self.type is DeusExTypes.POSITIVE: self.preActivateTimer.start(10000) else: self.preActivateTimer.start(3000) # activate timer self.activateTimer = QTimer() self.activateTimer.setTimerType(Qt.PreciseTimer) self.activateTimer.timeout.connect(self.endingSoundFinished) def boundingRect(self): return self.m_boundingRect def shape(self): return self.m_painterPath def paint(self, QPainter, QStyleOptionGraphicsItem, widget=None): pen = QPen() if self.type is DeusExTypes.POSITIVE: pen.setColor(Qt.darkGreen) else: pen.setColor(Qt.darkRed) brush = QBrush(self.rg) QPainter.setPen(pen) QPainter.setBrush(brush) QPainter.drawEllipse(self.m_boundingRect) def pulse(self): if self.rgCurrentRadius == 50: self.pulseSound.play() self.rgCurrentRadius += 20 self.rg.setCenterRadius(self.rgCurrentRadius) if self.rgCurrentRadius >= self.rgMaxRadius: self.rgCurrentRadius = self.rgMinRadius def preActivate(self): firstClr = QColor(Qt.yellow) firstClr.setAlphaF(0.7) secondClr = QColor(Qt.darkYellow) secondClr.setAlphaF(0.7) self.rg.setColorAt(0.0, firstClr) self.rg.setColorAt(1.0, secondClr) self.pulseSound.stop() self.pulseTimer.timeout.disconnect() self.preActivateTimer.timeout.disconnect() self.pulseTimer.stop() self.preActivateTimer.stop() # activate self.endingSound.play() self.activateTimer.start() def endingSoundFinished(self): if self.endingSound.get_state() == AL_STOPPED: deusExSignalData = DeusExSignalData(self.type) for obj in self.collidingItems(): if type(obj).__name__ == "Player": deusExSignalData.markedPlayers.append(obj) self.activateTimer.timeout.disconnect() self.activateTimer.stop() self.deusExActivateSignal.emit(deusExSignalData) self.scene().removeItem(self) del self
class Canvas(QWidget): """ Canvas class for PyQt5. Every time it is redrawn, it calls self.redraw. Every redraw is complete, pixel values are not remembered between redraws. There is self.frame_counter available that is incremented every timeout. self.is_timeout is only true for first redraw and redraws caused by self.timeout() """ def __init__(self, width=500, height=500, anim_period=-1, antialiasing=True, precise_timer=True): super().__init__() self.setFixedSize(width, height) self.anim_period = anim_period self.antialiasing = antialiasing self.p = QPainter() self.timer = QTimer(self) if precise_timer: self.timer.setTimerType(Qt.PreciseTimer) self.timer.timeout.connect(self.timeout) if anim_period >= 0: self.timer.start(anim_period) self.frame_counter = 0 self.is_timeout = True def timeout(self): self.frame_counter += 1 self.is_timeout = True self.update() def redraw(self): raise NotImplementedError("This method is abstract, you need to implement it.") def paintEvent(self, event): self.p.begin(self) if self.antialiasing: self.p.setRenderHint(QPainter.Antialiasing) self.redraw() self.p.end() self.is_timeout = False def keyPressEvent(self, e): if e.key() == Qt.Key_Space: if self.timer.isActive(): self.timer.stop() else: if self.anim_period >= 0: self.timer.start(self.anim_period) if e.key() == Qt.Key_S: if not self.timer.isActive(): self.timeout() if e.key() == Qt.Key_Escape: sys.exit()
ui.roomRedButton.clicked.connect(on_room_red) ui.roomBlueButton.clicked.connect(on_room_blue) ui.roomWarmButton.clicked.connect(on_room_warm) ui.roomVerticalSlider.valueChanged.connect(on_room_slider) ui.exitButton.clicked.connect(on_exit) ui.openWindowButton.clicked.connect(on_open_window) ui.closeWindowButton.clicked.connect(on_close_window) ui.autoWindowButton.clicked.connect(on_auto_window) Widget.showFullScreen() slidetimer = QTimer() slidetimer.setInterval(1500) slidetimer.setTimerType(Qt.PreciseTimer) slidetimer.timeout.connect(on_slidetimer) slidetimer.setSingleShot(True) timer = QTimer() timer.setInterval(1000) timer.setTimerType(Qt.PreciseTimer) timer.timeout.connect(on_timer) timestamp_start = datetime.now() timer.start(1000) Widget.setMouseTracking(True) eventFilter = UiEventFilter() app.installEventFilter(eventFilter)
class VideoModeWindow(object): def __init__(self, window, main_ui): self.main_ui = main_ui self.window = window # init signal and slot self.init_signal() self.sendMethod = None self.video = VideoProcess() # timer self.timer = QTimer(self.window) self.timer.timeout.connect(self.videoSend) self.lastTimeStamp = time.time() def init_signal(self): # connect signal and slot # Threshold value self.main_ui.spinBox_videoBWThreshold.valueChanged['int'].connect(self.main_ui.horizontalSlider_videoBWThreshold.setValue) self.main_ui.horizontalSlider_videoBWThreshold.valueChanged['int'].connect(self.main_ui.spinBox_videoBWThreshold.setValue) self.videoBWThresholdValue = 127 self.main_ui.spinBox_videoBWThreshold.valueChanged.connect(self.videoBWThresholdValueUpdate) self.videoPreviewBWSize = '256*128' self.main_ui.checkBox_videoPreviewBW2x.clicked.connect(self.previewBWSizeUpdate) # import image self.main_ui.pbt_videoOpenFile.clicked.connect(self.openVideo) self.lastFolderPath = '' # BM invert self.main_ui.checkBox_videoBWInvert.clicked.connect(self.BWInvertUpdate) self.videoBWInvert = False # preview mode self.main_ui.radioButton_videoPreviewRaw.clicked.connect(self.previewModeUpdate) self.main_ui.radioButton_videoPreviewGray.clicked.connect(self.previewModeUpdate) self.main_ui.radioButton_videoPreviewBW.clicked.connect(self.previewModeUpdate) self.previewMode = 'raw' self.main_ui.horizontalSlider_videoPreviewFrameOffset.valueChanged.connect(self.previewSilderUpdate) self.main_ui.horizontalSlider_videoPreviewFrameOffset.valueChanged['int'].connect(self.main_ui.spinBox_videoFrameOffset.setValue) self.main_ui.spinBox_videoFrameOffset.valueChanged['int'].connect(self.main_ui.horizontalSlider_videoPreviewFrameOffset.setValue) self.currentFrameIndex = 0 # send to OLED self.main_ui.pbt_videoStartSending.clicked.connect(self.videoSendStateUpdate) self.videoSendFramerate = 1 self.videoSending = False # disable widget before import video file self.setPreviewSliderAndSpinboxEnable(False) self.setVideoWidgetEnable(False) def setPreviewSliderAndSpinboxEnable(self, enable): self.main_ui.horizontalSlider_videoPreviewFrameOffset.setEnabled(enable) self.main_ui.spinBox_videoFrameOffset.setEnabled(enable) def setVideoWidgetEnable(self, enable): self.main_ui.spinBox_videoBWThreshold.setEnabled(enable) self.main_ui.horizontalSlider_videoBWThreshold.setEnabled(enable) self.main_ui.checkBox_videoBWInvert.setEnabled(enable) self.main_ui.checkBox_videoPreviewBW2x.setEnabled(enable) self.main_ui.radioButton_videoPreviewRaw.setEnabled(enable) self.main_ui.radioButton_videoPreviewGray.setEnabled(enable) self.main_ui.radioButton_videoPreviewBW.setEnabled(enable) self.main_ui.pbt_videoStartSending.setEnabled(enable) def openVideo(self): filename, filetype = QFileDialog.getOpenFileName(self.window, 'Select a video file', self.lastFolderPath) if filename != '': self.lastFolderPath = path.dirname(filename) self.main_ui.lineEdit_videoFileName.setText(filename) self.video.open(filename) w = self.video.frameWidth h = self.video.frameHeight k = w / h height = self.main_ui.label_videoPreviewWindow.height() width = int(k * height) - 1 self.video.setPreviewSize(width, height) self.video.requestFrame() self.main_ui.lineEdit_videoInfoFramerate.setText('%d' % (int(self.video.frameRate))) self.main_ui.lineEdit_videoInfoDuration.setText('%d|%.1f' % (int(self.video.frameCount), self.video.frameCount / self.video.frameRate)) self.main_ui.horizontalSlider_videoPreviewFrameOffset.setMinimum(0) self.main_ui.horizontalSlider_videoPreviewFrameOffset.setMaximum(int(self.video.frameCount)-1) self.main_ui.spinBox_videoFrameOffset.setMinimum(0) self.main_ui.spinBox_videoFrameOffset.setMaximum(int(self.video.frameCount)-1) self.main_ui.horizontalSlider_videoPreviewFrameOffset.setSliderPosition(0) self.setPreviewSliderAndSpinboxEnable(True) self.setVideoWidgetEnable(True) self.video.requestFrame() self.timer.setSingleShot(True) self.timer.start(100) def videoBWThresholdValueUpdate(self): self.videoBWThresholdValue = self.main_ui.spinBox_videoBWThreshold.value() if not self.timer.isActive(): self.video.setBWThreshold(value=self.videoBWThresholdValue) self.video.setIndex(self.currentFrameIndex) self.video.requestFrame() self.timer.setSingleShot(True) self.timer.start(50) def previewBWSizeUpdate(self): if self.main_ui.checkBox_videoPreviewBW2x.isChecked(): self.videoPreviewBWSize = '256*128' else: self.videoPreviewBWSize = '128*64' if not self.timer.isActive(): self.video.setIndex(self.currentFrameIndex) self.video.requestFrame() self.timer.setSingleShot(True) self.timer.start(50) def BWInvertUpdate(self): if self.main_ui.checkBox_videoBWInvert.isChecked(): self.video.setBWThreshold(invert=True) else: self.video.setBWThreshold(invert=False) if not self.timer.isActive(): self.video.setIndex(self.currentFrameIndex) self.video.requestFrame() self.timer.setSingleShot(True) self.timer.start(50) def previewModeUpdate(self): if self.main_ui.radioButton_videoPreviewRaw.isChecked(): self.previewMode = 'raw' elif self.main_ui.radioButton_videoPreviewGray.isChecked(): self.previewMode = 'gray' else: self.previewMode = 'BW' if not self.timer.isActive(): self.video.setIndex(self.currentFrameIndex) self.video.requestFrame() self.timer.setSingleShot(True) self.timer.start(50) def previewSilderUpdate(self): self.currentFrameIndex = self.main_ui.horizontalSlider_videoPreviewFrameOffset.value() if not self.timer.isActive(): self.video.setIndex(self.currentFrameIndex) self.video.requestFrame() self.timer.setSingleShot(True) self.timer.start(50) def updatePreview(self): # update preview if self.previewMode == 'raw': image = self.image_raw elif self.previewMode == 'gray': image = self.image_gray else: image = self.image_bw image_pix = OpenCVImage2QPixMap(image) self.main_ui.label_videoPreviewWindow.setPixmap(QPixmap.fromImage(image_pix)) def updateBWPreview(self): if self.videoPreviewBWSize == '256*128': image = self.image_out_bw2x else: image = self.image_out_bw image_pix = OpenCVImage2QPixMap(image) self.main_ui.label_videoPreviewOutputBW.setPixmap(QPixmap.fromImage(image_pix)) def videoSendStateUpdate(self): if self.videoSending: self.videoSending = False self.setPreviewSliderAndSpinboxEnable(True) self.main_ui.pbt_videoStartSending.setText('Start Sending') self.timer.stop() self.video.clearFrames() else: self.videoSending = True self.setPreviewSliderAndSpinboxEnable(False) self.video.clearFrames() self.video.setIndex(self.currentFrameIndex) self.video.requestFrame() self.main_ui.pbt_videoStartSending.setText('Stop Sending') timeout = int(1000 / self.main_ui.spinBox_videoSendFramerate.value()) self.timer.setInterval(timeout) self.timer.setTimerType(0) self.timer.setSingleShot(False) self.timer.start() self.lastTimeStamp = time.time() def readAllImages(self): result = self.video.readFrames() if result != None: self.image_raw, self.image_gray, self.image_bw, self.image_out_bw, self.image_out_bw2x, self.image_data_frame = result return True else: return False def videoSend(self): if self.readAllImages(): self.updatePreview() self.updateBWPreview() if self.videoSending: self.video.requestFrame() self.currentFrameIndex += 1 self.send(self.image_data_frame) self.lastTimeStamp = time.time() self.main_ui.horizontalSlider_videoPreviewFrameOffset.setSliderPosition(self.currentFrameIndex) self.main_ui.spinBox_videoFrameOffset.setValue(self.currentFrameIndex) if self.currentFrameIndex + 1 >= int(self.video.frameCount): self.videoSendStateUpdate() def addSendMethod(self, sendMethod): self.sendMethod = sendMethod def send(self, data): if self.sendMethod != None: self.sendMethod.send(data) def exit(self): if self.video != None: self.video.stop()
class MainWindow(QWidget): def __init__(self, parent=None, flags=Qt.WindowFlags()): super(MainWindow, self).__init__(parent=parent, flags=flags) self.subcarriers_index = get_subcarriers_index(bw=BW, ng=NG) self.configUI() self.configLayout() self.show() def configUI(self): self.setWindowTitle("CSIVIEWER") self.setWindowFlags(Qt.WindowCloseButtonHint | Qt.WindowMinimizeButtonHint | Qt.WindowMaximizeButtonHint) self.resize(800, 450) self.move(100, 100) self.setStyleSheet("border:0;background-color:#e8e8e8") # pygtgrapg global config pg.setConfigOptions(antialias=True) # Antialiasing pg.setConfigOptions(background=(232, 232, 232)) # White pg.setConfigOptions(foreground=(25, 25, 25)) # Black # plot area self.plot = pg.GraphicsWindow() self.initPlot() # ctrl area self.ctrl = QMenuBar(self) self.initCtrl() # update cache self.task = GetDataThread(self) self.task.start() # plot refresh self.PlotTimer() def configLayout(self): layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self.plot) layout.addWidget(self.ctrl) @staticmethod def color_define(index): return (int(np.sin(index * (2 * np.pi) / 30 + 2) * 127 + 128), int(np.cos(index * (2 * np.pi) / 30 - 1) * 127 + 128), int(np.sin(index * (2 * np.pi) / 30 - 2) * 127 + 128), 127) def initPlot(self): """Plot here""" # csi amplitude (time view) ====================================================== pk_num = cache_amptiA.shape[1] p = self.plot.addPlot(title="Amplitude of CSI_800_%s_0_0" % (SUBCARRIERS_NUM)) p.showGrid(x=False, y=True) p.setLabel('left', "Amplitude") p.setLabel('bottom', "Packets", units='') p.enableAutoRange('xy', False) p.setXRange(-int(pk_num / 2), int(pk_num / 2), padding=0.01) p.setYRange(0, YRange_A, padding=0.01) self.curvesA = [] for i in range(SUBCARRIERS_NUM): color = self.color_define(i) self.curvesA.append(p.plot(pen=color, name='subcarrier=%d' % (i))) # x axis self.X = np.linspace(-int(pk_num / 2), int(pk_num / 2), pk_num) # csi amplitude (subcarriers view) =============================================== p2 = self.plot.addPlot(title="CFR of CSI_10_%s_3_0" % (SUBCARRIERS_NUM)) p2.showGrid(x=False, y=True) p2.setLabel('left', "Amplitude") p2.setLabel('bottom', "Subcarriers", units='') p2.enableAutoRange('xy', False) p2.setXRange(-30, 30, padding=0.01) p2.setYRange(0, YRange_B, padding=0.01) self.curvesB = [] for i in range(10 * 3): color = self.color_define(i) self.curvesB.append(p2.plot(pen=color)) self.plot.nextRow() # csi phase (subcarriers view) =================================================== p3 = self.plot.addPlot(title="Phase of CSI_10_%s_3_0" % (SUBCARRIERS_NUM)) p3.showGrid(x=False, y=True) p3.setLabel('left', "Phase") p3.setLabel('bottom', "Subcarriers", units='') p3.enableAutoRange('xy', False) p3.setXRange(-30, 30, padding=0.01) p3.setYRange(-np.pi, np.pi, padding=0.01) self.curvesC = [] for i in range(10 * 3): color = self.color_define(i) self.curvesC.append(p3.plot(pen=color)) # cir csi (time view) ============================================================ p4 = self.plot.addPlot(title="CIR of CSI_10_%s_3_0" % (SUBCARRIERS_NUM)) p4.showGrid(x=False, y=True) p4.setLabel('left', "Amplitude") p4.setLabel('bottom', "Time", units='50ns') p4.enableAutoRange('xy', False) p4.setXRange(0, 64, padding=0.01) p4.setYRange(0, YRange_D, padding=0.01) self.curvesD = [] for i in range(10 * 3): color = self.color_define(i) self.curvesD.append(p4.plot(pen=color)) def initCtrl(self): """Write your control here.""" self.ctrl.addMenu("&File") self.ctrl.addMenu("&Edit") self.ctrl.addMenu("&Help") self.ctrl.setVisible(False) def PlotTimer(self): self.timer = QTimer() self.timer.setTimerType(0) self.timer.timeout.connect(self.updatePlot) self.timer.start(1000 // 30) @pyqtSlot() def updatePlot(self): global cache_amptiA, cache_amptiB, cache_phaseC, cache_cirD, mutex, state mutex.lock() if state: for i in range(SUBCARRIERS_NUM): self.curvesA[i].setData(self.X, cache_amptiA[i]) for i in range(10 * 3): self.curvesB[i].setData(self.subcarriers_index, cache_amptiB[:, i % 10, i // 10]) self.curvesC[i].setData(self.subcarriers_index, cache_phaseC[:, i % 10, i // 10]) self.curvesD[i].setData(np.arange(64), cache_cirD[:, i % 10, i // 10]) state = False mutex.unlock() def keyPressEvent(self, event): if event.key() == Qt.Key_F11: self.setWindowState(self.windowState() ^ Qt.WindowFullScreen) if event.key() == Qt.Key_F1: self.ctrl.setVisible(not self.ctrl.isVisible()) event.accept() def closeEvent(self, event): self.task.stop() event.accept()
class MissionPage(QWidget): mission_ended = pyqtSignal() def __init__(self, parent=None): super().__init__(parent) self.record = -1 self.inspected = None self.oob_update = False prefs = QSettings() prefs.beginGroup("/General") timeout = prefs.value("/Timeout") dark_mode = prefs.value("/DarkMode") prefs.endGroup() # Instantiate core objects self.timeout_timer = QTimer() self.timeout_timer.setTimerType(Qt.VeryCoarseTimer) self.timeout_timer.setInterval(timeout * 1000) self.timeout_timer.setSingleShot(True) self.timeout_timer.timeout.connect(self.update_temp_log) self.systems = ActionsWidget(LogSource.SYSTEM) self.systems.acted.connect(self.log_item) self.events = ActionsWidget(LogSource.EVENT) self.events.acted.connect(self.log_item) self.compass = Compass() self.compass_widget = QWidget() compass_layout = QHBoxLayout() self.compass_widget.setLayout(compass_layout) compass_layout.addWidget(self.compass) self.compass.angle_event.connect(self.log_item) self.exact_angle = ExactAngle() self.exact_angle_widget = QWidget() exact_angle_layout = QHBoxLayout() self.exact_angle_widget.setLayout(exact_angle_layout) exact_angle_layout.addWidget(self.exact_angle) self.exact_angle.btn_event.connect(self.reset_timer) self.exact_angle.angle_event.connect(self.log_item) tab_widget = QTabWidget() tab_bar = tab_widget.tabBar() tab_bar.setFont(QFont('Consolas', 12, 3)) tab_widget.addTab(self.compass_widget, "Compass") tab_widget.addTab(self.exact_angle_widget, "Precise Angle") tab_widget.setStyleSheet(""" QTabWidget::pane { border-top: 2px solid #C2C7CB; } /* Style the tab using the tab sub-control. Note that it reads QTabBar _not_ QTabWidget */ QTabBar::tab { background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #E1E1E1, stop: 0.4 #DDDDDD, stop: 0.5 #D8D8D8, stop: 1.0 #D3D3D3); border: 2px solid #C4C4C3; border-bottom-color: #C2C7CB; /* same as the pane color */ border-top-left-radius: 4px; border-top-right-radius: 4px; min-width: 8ex; padding: 2px; color: black; } QTabBar::tab:selected, QTabBar::tab:hover { background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #fafafa, stop: 0.4 #f4f4f4, stop: 0.5 #e7e7e7, stop: 1.0 #fafafa); } QTabBar::tab:selected { border-color: #ff0000; border-bottom-color: #C2C7CB; /* same as pane color */ } QTabBar::tab:!selected { margin-top: 2px; /* make non-selected tabs look smaller */ } """) header_layout = QHBoxLayout() self.zulu_time_label = QLabel() self.assessor_label = QLabel() self.date_label = QLabel() self.dl_label = QLabel() self.mnemonic_label = QLabel() header_layout.addWidget(self.zulu_time_label) header_layout.addWidget(self.assessor_label) header_layout.addWidget(self.date_label) header_layout.addWidget(self.dl_label) header_layout.addWidget(self.mnemonic_label) res = QApplication.primaryScreen().size() w, h = res.width(), res.height() if w > 1920 or h > 1080: hdr_font = QFont("Consolas", 16, 2) end_font = QFont("Consolas", 32, 5) else: hdr_font = QFont("Consolas", 14, 2) end_font = QFont("Consolas", 28, 5) for index in range(header_layout.count()): widget = header_layout.itemAt(index).widget() widget.setSizePolicy( QSizePolicy.Preferred, QSizePolicy.Maximum ) widget.setFont(hdr_font) widget.setAlignment(Qt.AlignCenter) # Setup logging state machine self.init_log_sm() # Setup splitters actions_splitter = QSplitter( Qt.Horizontal, frameShape=QFrame.StyledPanel, frameShadow=QFrame.Plain ) actions_splitter.addWidget(self.systems) actions_splitter.addWidget(self.events) actions_splitter.addWidget(tab_widget) actions_splitter.setChildrenCollapsible(False) main_splitter = QSplitter( Qt.Vertical, frameShape=QFrame.StyledPanel, frameShadow=QFrame.Plain ) self.log_area = QTableWidget(0, 3) self.log_area.cellDoubleClicked.connect(self.entry_inspected) self.log_area.cellChanged.connect(self.entry_changed) self.log_area.setHorizontalHeaderLabels( ["Time", "System", "Events"] ) self.log_area.horizontalHeader().setStretchLastSection(True) self.set_dark_mode(dark_mode) end_msn_btn = QPushButton("END\r\nMISSION") end_msn_btn.clicked.connect(self.end_mission) end_msn_btn.setFont(end_font) end_msn_btn.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) end_msn_btn.setStyleSheet("background-color: red; color: white") bottom_layout = QGridLayout() bottom_widget = QWidget() bottom_widget.setLayout(bottom_layout) bottom_layout.addWidget(self.log_area, 0, 0, 1, 7) bottom_layout.addWidget(end_msn_btn, 0, 8, 1, 1) main_splitter.addWidget(actions_splitter) main_splitter.addWidget(bottom_widget) main_splitter.setChildrenCollapsible(False) handle_css = """ QSplitter::handle { background-image: url(:/imgs/dot_pattern.png); background-repeat: repeat-xy; background-color: none; border: 1px solid gray; } QSplitter::handle:pressed { background-image: url(:/imgs/pressed.png); } """ actions_splitter.setStyleSheet(handle_css) main_splitter.setStyleSheet(handle_css) # Finalize layout main_layout = QVBoxLayout() main_layout.addLayout(header_layout) main_layout.addWidget(main_splitter) self.setLayout(main_layout) def load_mission(self, config, timer, time, recovered=None): cfg_systems = [] cfg_events = [] for system in config['systems']: cfg_systems.append(system) for event in config['events']: cfg_events.append(event) self.systems.add_actions(cfg_systems) self.events.add_actions(cfg_events) self.timer = timer self.timer.timeout.connect(self.inc_time) self.time = time self.assessor = config['assessor'] self.assessor_label.setText("Assessor: " + self.assessor) self.date = config['date'] self.date_label.setText("Date: " + self.date) self.dl = config['dl'] self.dl_label.setText("Mission: DL-" + self.dl) self.mnemonic = config['mnemonic'] self.mnemonic_label.setText("Mnemonic: " + self.mnemonic) date = QDate.fromString(self.date, "dd/MM/yyyy").toString("yyyyMMdd") self.file_name = f"DL-{self.dl} {self.mnemonic} {date}" temp_path = Path(__file__).parents[1] / "temp" temp_cfg = temp_path / f"{self.file_name}.cfg" os.makedirs(os.path.dirname(temp_cfg), exist_ok=True) self.temp_log = temp_path / f"{self.file_name}.csv" os.makedirs(os.path.dirname(self.temp_log), exist_ok=True) if temp_cfg: with open(temp_cfg, 'w') as save_cfg_file: save_cfg_file.write(json.dumps(config)) else: QMessageBox.critical( self, "Error", f"Unable to load recovered config file: {temp_cfg}" ) return if recovered: self.recover_log(recovered) def recover_log(self, log): with open(log, 'r', newline='') as infile: reader = csv.reader(infile, delimiter=',') for row in reader: self.record = self.record + 1 self.log_area.insertRow(self.record) self.log_area.setItem( self.record, 0, QTableWidgetItem(row[0]) ) self.log_area.setItem( self.record, 1, QTableWidgetItem(row[1]) ) self.log_area.setItem( self.record, 2, QTableWidgetItem(row[2]) ) # TODO: This isn't working... self.log_area.scrollToBottom() @pyqtSlot() def inc_time(self): self.time = self.time.addSecs(1) self.zulu_time_label.setText( "Time: {} ZULU".format(self.time.toString("HH:mm:ss")) ) def init_log_sm(self): self.log_state = QStateMachine() pre_system = QState() pre_event = QState() post_event = QState() self.log_state.addState(pre_system) self.log_state.addState(pre_event) self.log_state.addState(post_event) self.log_state.setInitialState(pre_system) pre_system.assignProperty(self.events, "enabled", False) pre_system.assignProperty(self.compass, "enabled", False) pre_system.assignProperty(self.exact_angle, "enabled", False) pre_event.assignProperty(self.events, "enabled", True) pre_event.assignProperty(self.compass, "enabled", False) pre_event.assignProperty(self.exact_angle, "enabled", False) post_event.assignProperty(self.compass, "enabled", True) post_event.assignProperty(self.exact_angle, "enabled", True) pre_system.addTransition( self.systems.acted, pre_event ) pre_system.addTransition(self.timeout_timer.timeout, pre_system) pre_event.addTransition(self.timeout_timer.timeout, pre_system) post_event.addTransition(self.timeout_timer.timeout, pre_system) pre_event.addTransition( self.systems.acted, pre_event ) post_event.addTransition( self.systems.acted, pre_event ) post_event.addTransition( self.events.acted, post_event ) pre_event.addTransition( self.events.acted, post_event ) pre_system.entered.connect(self.events.switch_active) pre_system.entered.connect(self.systems.switch_active) pre_event.entered.connect(self.events.switch_active) post_event.exited.connect(self.compass.clear_state) post_event.exited.connect(lambda: self.exact_angle.log_angle(False)) self.log_state.setRunning(True) def log_system(self, system): self.record = self.record + 1 event_time = self.time.toString("HH:mm:ss") self.log_area.insertRow(self.record) self.log_area.setItem( self.record, 0, QTableWidgetItem(event_time) ) self.log_area.setItem( self.record, 1, QTableWidgetItem(system) ) self.log_area.setItem( self.record, 2, QTableWidgetItem("") ) self.log_area.scrollToBottom() def log_event(self, event): current = self.log_area.item(self.record, 2).text() if len(current) > 0: current = current + "; " current = current + event self.log_area.setItem( self.record, 2, QTableWidgetItem(current) ) def log_compass(self, range): current = self.log_area.item(self.record, 2).text() current = current + range self.log_area.setItem( self.record, 2, QTableWidgetItem(current) ) def log_angle(self, angle): current = self.log_area.item(self.record, 2).text() current = f"{current} at {angle}°" self.log_area.setItem( self.record, 2, QTableWidgetItem(current) ) @pyqtSlot(Angle) @pyqtSlot(int) def log_item(self, data): last_unlogged = self.timeout_timer.isActive() self.timeout_timer.start() src = self.sender() if type(src) is ActionsWidget: if self.exact_angle.has_valid(): if self.exact_angle.is_valid(): self.log_angle(self.exact_angle.calc_angle()) self.exact_angle.clear_state() if src.source is LogSource.SYSTEM: self.log_system(src.get_action(data)) if last_unlogged: self.update_temp_log() elif src.source is LogSource.EVENT: self.log_event(src.get_action(data)) elif type(src) is Compass: self.log_compass(Angle.to_string(data)) elif type(src) is ExactAngle: self.log_angle(str(data)) @pyqtSlot(int, int) def entry_inspected(self, row, col): self.inspected = row, col @pyqtSlot(int, int) def entry_changed(self, row, col): if (row, col) == self.inspected: if not self.timeout_timer.isActive(): self.update_temp_log(False) elif row == self.record - 1: self.oob_update = True def update_temp_log(self, append=True): if self.oob_update: append = False self.oob_update = False if self.temp_log: if append: with open(self.temp_log, 'a', newline='') as outfile: writer = csv.writer(outfile) rowdata = [] row = self.log_area.rowCount() - 1 for column in range(self.log_area.columnCount()): item = self.log_area.item(row, column) if item is not None: rowdata.append( item.text() ) else: rowdata.append('') writer.writerow(rowdata) else: with open(self.temp_log, 'w', newline='') as outfile: writer = csv.writer(outfile) for row in range(self.log_area.rowCount()): rowdata = [] for column in range(self.log_area.columnCount()): item = self.log_area.item(row, column) if item is not None: rowdata.append( item.text() ) else: rowdata.append('') writer.writerow(rowdata) def save_log(self): path, _ = QFileDialog.getSaveFileName( self, 'Save File', self.file_name, 'CSV(*.csv)' ) if path: with open(path, 'w', newline='') as outfile: writer = csv.writer(outfile) for row in range(self.log_area.rowCount()): rowdata = [] for column in range(self.log_area.columnCount()): item = self.log_area.item(row, column) if item is not None: rowdata.append( item.text() ) else: rowdata.append('') writer.writerow(rowdata) return True return False @pyqtSlot() def end_mission(self): quit_prompt = QMessageBox.question( self, "End mission?", "If you choose to end this mission, the time hack will end and logging will stop. Really end?" # noqa: E501 ) if quit_prompt == QMessageBox.Yes: if self.save_log(): QMessageBox.information( self, "Mission Ended", "Mission has been ended and your logs have been saved." ) temp_path = Path(__file__).parents[1] / "temp" log_files = [ file for file in temp_path.rglob("*.csv") if file.is_file() ] cfg_files = [ file for file in temp_path.rglob("*.cfg") if file.is_file() ] if log_files and cfg_files: try: for file in log_files + cfg_files: file.unlink() except OSError as e: QMessageBox.critical( self, "Error", f"Error encountered attempting to delete temp files: { e.strerror }" # noqa: E501 ) self.mission_ended.emit() @pyqtSlot() def reset_timer(self): self.timeout_timer.start() @pyqtSlot(int) def set_timeout(self, timeout): self.timeout_timer.setInterval(timeout * 1000) @pyqtSlot(QSize) def window_resized(self, size): self.events.resize(size.height()) self.systems.resize(size.height()) @pyqtSlot(int) def set_dark_mode(self, enable): if enable: self.log_area.setStyleSheet("QTableWidget::item { color: white }") else: self.log_area.setStyleSheet("QTableWidget::item { color: none }")
def startMoving(self): # print(self.dotsAnimations) timer = QTimer() timer.setTimerType(0) for i, animation in enumerate(self.dotsAnimations): timer.singleShot(i * self.separationTime, animation.start)
class MainWindow(QWidget): def __init__(self, parent=None, flags=Qt.WindowFlags()): super(MainWindow, self).__init__(parent=parent, flags=flags) self.configUI() self.configLayout() self.show() def configUI(self): screen_size = QApplication.primaryScreen().size() self.setWindowTitle("CSIRatioA") self.setWindowFlags(Qt.WindowCloseButtonHint | Qt.WindowMinimizeButtonHint | Qt.WindowMaximizeButtonHint) self.resize(800, 450) self.move(screen_size.width() - 800 - 100, 100) # pygtgrapg global config pg.setConfigOptions(antialias=True) # Antialiasing pg.setConfigOptions(foreground=(232, 232, 232)) # Black pg.setConfigOptions(background=(25, 25, 25)) # White # plot area self.win = pg.GraphicsWindow() # plot init self.initPlot() # update cache self.task = GetDataThread(self) self.task.start() # plot refresh self.PlotTimer() def configLayout(self): layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.win) def initPlot(self): lens = cache.shape[-1] p = self.win.addPlot(title="Ratio of CSI(30x)") p.showGrid(x=False, y=True) p.setLabel('left', "Amplitude", units='A') p.setLabel('bottom', "Packets", units='') p.enableAutoRange('xy', False) p.setXRange(-int(lens/2), int(lens/2), padding=0.01) p.setYRange(-10, 70, padding=0.01) p.hideAxis('left') # p.addLegend() # curves self.curves = [] for i in range(len(cache)): color = (int(np.sin(i * (2 * np.pi) / 30 + 2) * 127 + 128), int(np.cos(i * (2 * np.pi) / 30 - 1) * 127 + 128), int(np.sin(i * (2 * np.pi) / 30 - 2) * 127 + 128), 127) self.curves.append(p.plot(pen=color, name='subcarrier=%d' % (i))) # x axis self.X = np.linspace(-int(lens/2), int(lens/2), lens) def PlotTimer(self): self.timer = QTimer() self.timer.setTimerType(0) self.timer.timeout.connect(self.updatePlot) self.timer.start(1000//30) @pyqtSlot() def updatePlot(self): global cache, mutex mutex.lock() for i in range(len(cache)): xscale = 1 - i / 200. self.curves[i].setData(xscale * self.X, i/2 + cache[i]) mutex.unlock() def closeEvent(self, event): self.task.stop() event.accept()