我有以下状态机:
我已经通过标准状态模式在 Python 中实现了这一点。该状态机在与 PySide6 应用程序线程不同的线程中运行,而 PySide6 应用程序线程在程序的主线程上运行。我们称这个状态机类为
Controller
。
在 GUI 方面,我使用 Qt for Python 状态机框架。 (注意:链接的文档适用于 PySide2/PySide5,但我使用的是 Pyside6。出于某种原因,PySide6 不存在相同的文档。)这使得定义进入状态时 GUI 中发生的情况非常好.
我正在寻找的是不是让GUI启动转换,而是让我的核心状态机
Controller
在另一个线程中运行来控制转换。通常,人们使用 <QState>.addTransition(...)
在 GUI 级别添加转换,但我希望 GUI 简单地将命令发送到 Controller
,然后当 Controller
转换其状态时,我希望它发出 Signal
触发 PySide6 QStateMachine
进入给定状态,从而设置适合该状态的所有属性。换句话说,我希望 GUI 将命令分派到 Controller
状态机,并让 GUI “监听”Controller
的状态转换。
所以问题是,给定一个
QState
,我如何向它发送一个信号来强制它所属的 QStateMachine
转换到该状态?或者我是否需要向 QStateMachine
发送信号并为其提供 QState
来转换到?
示例程序:
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import override
from PySide6.QtStateMachine import QState
class Controller:
def __init__(self, initial_state: IState, states: list[QState]) -> None:
"""Initialize the controller to the given initial state and call the `
on_entry` method for the state.
"""
self.__state = initial_state
self.__state.controller = self
self.__state.on_entry()
self.__state.states = states
def _transition_to(self, new_state: IState) -> None:
"""Transition from the current state to the given new state. This calls
the `on_exit` method on the current state and the `on_entry` of the
new state. This method should not be called by any object other than
concrete implementations of `IState`.
"""
self.__state.on_exit()
self.__state = new_state
self.__state.controller = self
self.__state.on_entry()
@property
def state(self):
"""Get the current state"""
return self.__state
def start_camera_exposure(self) -> None:
self.__state.start_camera_exposure()
def stop_camera_exposure(self) -> None:
self.__state.stop_camera_exposure()
def abort_camera_exposure(self) -> None:
self.__state.abort_camera_exposure()
class IState(ABC):
"""Serve as an interface between the controller and the explicit, individual states."""
@property
def controller(self) -> Controller:
return self.__controller
@controller.setter
def controller(self, controller: Controller) -> None:
self.__controller = controller
@property
def states(self) -> list[QState]:
return self.__states
@states.setter
def states(self, states: list[QState]):
self.__states = states
def on_entry(self) -> None:
"""Can be overridden by a state to perform an action when the state is
being entered, i.e., transitions into. It is not required to be overridden.
"""
pass
def on_exit(self) -> None:
"""Can be overridden by a state to perform an action when the state is
being exited, i.e., transitioned from. It is not required to be overridden.
"""
pass
# If a concrete implementation does not handle the called method, i.e., it is an invalid action
# in the specific state, it is enough to simply call `pass`.
@abstractmethod
def start_camera_exposure(self) -> None: ...
@abstractmethod
def stop_camera_exposure(self) -> None: ...
@abstractmethod
def abort_camera_exposure(self) -> None: ...
class Idle(IState):
@override
def on_entry(self):
# I want to emit a signal here to force a `QStateMachine` to go to state: `self.__states[0]`
print("Idling ...")
def start_camera_exposure(self) -> None:
self.controller._transition_to(CameraExposing())
def stop_camera_exposure(self) -> None:
pass
def abort_camera_exposure(self) -> None:
pass
class CameraExposing(IState):
@override
def on_entry(self) -> None:
# I want to emit a signal here to force a `QStateMachine` to go to state: `self.__states[1]`
print("Starting camera exposure ...")
@override
def on_exit(self) -> None:
print("Stopping camera exposure ...")
def start_camera_exposure(self) -> None:
pass
def stop_camera_exposure(self) -> None:
self.controller._transition_to(SavingCameraImages())
def abort_camera_exposure(self) -> None:
self.controller._transition_to(AbortingCameraExposure())
class SavingCameraImages(IState):
@override
def on_entry(self) -> None:
# I want to emit a signal here to force a `QStateMachine` to go to state: `self.__states[2]`
print("Saving camera images ...")
self.controller._transition_to(Idle())
def start_camera_exposure(self) -> None:
pass
def stop_camera_exposure(self) -> None:
pass
def abort_camera_exposure(self) -> None:
pass
class AbortingCameraExposure(IState):
@override
def on_entry(self) -> None:
# I want to emit a signal here to force a `QStateMachine` to go to state: `self.__states[3]`
print("Aborting camera exposure ...")
self.controller._transition_to(Idle())
def start_camera_exposure(self) -> None:
pass
def stop_camera_exposure(self) -> None:
pass
def abort_camera_exposure(self) -> None:
pass
在 GUI 方面,我有类似的东西:
from PySide6.QtStateMachine import QState, QStateMachine
class MainWindow(QWidget):
def __init__(self) -> None:
super().__init__()
self.machine = QStateMachine(parent=self)
self.state_idle = QState(self.machine)
self.state_camera_exposing = QState(self.machine)
self.state_saving_camera_images = QState(self.machine)
self.state_aborting_camera_exposure = QState(self.machine)
self.machine.setInitialState(self.state_idle)
self.states = [
self.state_idle,
self.state_camera_exposing,
self.state_saving_camera_images,
self.state_aborting_camera_exposure,
]
self.initialize()
到目前为止,我想出的唯一方法是通过简单地为每个状态的每个转换信号创建添加所有可能的转换。
在
class MainWindow(QWidget)
内部,定义类变量来表示各种转换信号:
transition_to_idle = Signal()
transition_to_camera_exposing = Signal()
transition_to_saving_camera_images = Signal()
transition_to_aborting_camera_exposure = Signal()
然后为每个状态添加每个信号的转换,转换到由信号确定的状态:
machine = self.machine
state_idle = self.state_idle
state_camera_exposing = self.state_camera_exposing
state_saving_camera_images = self.state_saving_camera_images
state_aborting_camera_exposure = self.state_aborting_camera_exposure
state_idle.addTransition(self.transition_to_idle, state_idle)
state_idle.addTransition(self.transition_to_camera_exposing, state_camera_exposing)
state_idle.addTransition(self.transition_to_saving_camera_images, state_saving_camera_images)
state_idle.addTransition(self.transition_to_aborting_camera_exposure, state_aborting_camera_exposure)
state_camera_exposing.addTransition(self.transition_to_idle, state_idle)
state_camera_exposing.addTransition(self.transition_to_camera_exposing, state_camera_exposing)
state_camera_exposing.addTransition(self.transition_to_saving_camera_images, state_saving_camera_images)
state_camera_exposing.addTransition(self.transition_to_aborting_camera_exposure, state_aborting_camera_exposure)
state_saving_camera_images.addTransition(self.transition_to_idle, state_idle)
state_saving_camera_images.addTransition(self.transition_to_camera_exposing, state_camera_exposing)
state_saving_camera_images.addTransition(self.transition_to_saving_camera_images, state_saving_camera_images)
state_saving_camera_images.addTransition(self.transition_to_aborting_camera_exposure, state_aborting_camera_exposure)
state_aborting_camera_exposure.addTransition(self.transition_to_idle, state_idle)
state_aborting_camera_exposure.addTransition(self.transition_to_camera_exposing, state_camera_exposing)
state_aborting_camera_exposure.addTransition(self.transition_to_saving_camera_images, state_saving_camera_images)
state_aborting_camera_exposure.addTransition(self.transition_to_aborting_camera_exposure, state_aborting_camera_exposure)
emit
: 发出信号
transition_to_idle.emit()
transition_to_camera_exposing.emit()
transition_to_saving_camera_images.emit()
transition_to_aborting_camera_exposure.emit()