Threading module with Communicator added
This commit is contained in:
@@ -17,14 +17,15 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
class Context:
|
class Context:
|
||||||
|
|
||||||
def __init__(self, initial_state):
|
def __init__(self):
|
||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
self.state = WelcomeState()
|
||||||
self.state = initial_state
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
@@ -37,11 +38,17 @@ class Context:
|
|||||||
if not isinstance(new_state, State):
|
if not isinstance(new_state, State):
|
||||||
raise TypeError('new_state must implement State')
|
raise TypeError('new_state must implement State')
|
||||||
|
|
||||||
|
logging.debug('New state is "{}"'.format(new_state))
|
||||||
|
|
||||||
|
self._state = new_state
|
||||||
|
|
||||||
def handleEvent(self, event):
|
def handleEvent(self, event):
|
||||||
|
|
||||||
if not isinstance(event, Event):
|
if not isinstance(event, Event):
|
||||||
raise TypeError('event must implement Event')
|
raise TypeError('event must implement Event')
|
||||||
|
|
||||||
|
logging.debug('Handling event "{}"'.format(event))
|
||||||
|
|
||||||
if isinstance(event, ErrorEvent):
|
if isinstance(event, ErrorEvent):
|
||||||
self.state = ErrorState(event.exception, self.state)
|
self.state = ErrorState(event.exception, self.state)
|
||||||
elif isinstance(event, TeardownEvent):
|
elif isinstance(event, TeardownEvent):
|
||||||
@@ -109,7 +116,7 @@ class TeardownEvent(Event):
|
|||||||
def __init__(self, target):
|
def __init__(self, target):
|
||||||
|
|
||||||
self._target = target
|
self._target = target
|
||||||
super().__init__('Teardown')
|
super().__init__('Teardown({})'.format(target))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target(self):
|
def target(self):
|
||||||
@@ -168,6 +175,10 @@ class ErrorState(State):
|
|||||||
self.old_state = old_state
|
self.old_state = old_state
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
|
||||||
|
return 'ErrorState'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def old_state(self):
|
def old_state(self):
|
||||||
|
|
||||||
@@ -198,6 +209,10 @@ class TeardownState(State):
|
|||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
|
||||||
|
return 'TeardownState'
|
||||||
|
|
||||||
|
|
||||||
class WelcomeState(State):
|
class WelcomeState(State):
|
||||||
|
|
||||||
@@ -205,6 +220,10 @@ class WelcomeState(State):
|
|||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
|
||||||
|
return 'WelcomeState'
|
||||||
|
|
||||||
def handleEvent(self, event, context):
|
def handleEvent(self, event, context):
|
||||||
|
|
||||||
if isinstance(event, GuiEvent):
|
if isinstance(event, GuiEvent):
|
||||||
@@ -226,6 +245,10 @@ class SettingsState(State):
|
|||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
|
||||||
|
return 'SettingsState'
|
||||||
|
|
||||||
def handleEvent(self, event, context):
|
def handleEvent(self, event, context):
|
||||||
|
|
||||||
if isinstance(event, GuiEvent) and event.name == 'welcome':
|
if isinstance(event, GuiEvent) and event.name == 'welcome':
|
||||||
@@ -240,6 +263,10 @@ class StartupState(State):
|
|||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
|
||||||
|
return 'StartupState'
|
||||||
|
|
||||||
def handleEvent(self, event, context):
|
def handleEvent(self, event, context):
|
||||||
|
|
||||||
if isinstance(event, CameraEvent) and event.name == 'ready':
|
if isinstance(event, CameraEvent) and event.name == 'ready':
|
||||||
@@ -254,6 +281,10 @@ class IdleState(State):
|
|||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
|
||||||
|
return 'IdleState'
|
||||||
|
|
||||||
def handleEvent(self, event, context):
|
def handleEvent(self, event, context):
|
||||||
|
|
||||||
if ((isinstance(event, GuiEvent) or isinstance(event, GpioEvent)) and
|
if ((isinstance(event, GuiEvent) or isinstance(event, GpioEvent)) and
|
||||||
@@ -269,6 +300,10 @@ class GreeterState(State):
|
|||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
|
||||||
|
return 'GreeterState'
|
||||||
|
|
||||||
def handleEvent(self, event, context):
|
def handleEvent(self, event, context):
|
||||||
|
|
||||||
if ((isinstance(event, GuiEvent) or isinstance(event, GpioEvent)) and
|
if ((isinstance(event, GuiEvent) or isinstance(event, GpioEvent)) and
|
||||||
@@ -284,6 +319,10 @@ class CountdownState(State):
|
|||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
|
||||||
|
return 'CountdownState'
|
||||||
|
|
||||||
def handleEvent(self, event, context):
|
def handleEvent(self, event, context):
|
||||||
|
|
||||||
if isinstance(event, GuiEvent) and event.name == 'capture':
|
if isinstance(event, GuiEvent) and event.name == 'capture':
|
||||||
@@ -298,6 +337,10 @@ class CaptureState(State):
|
|||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
|
||||||
|
return 'CaptureState'
|
||||||
|
|
||||||
def handleEvent(self, event, context):
|
def handleEvent(self, event, context):
|
||||||
|
|
||||||
if isinstance(event, CameraEvent) and event.name == 'next':
|
if isinstance(event, CameraEvent) and event.name == 'next':
|
||||||
@@ -314,6 +357,10 @@ class AssembleState(State):
|
|||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
|
||||||
|
return 'AssembleState'
|
||||||
|
|
||||||
def handleEvent(self, event, context):
|
def handleEvent(self, event, context):
|
||||||
|
|
||||||
if isinstance(event, CameraEvent) and event.name == 'review':
|
if isinstance(event, CameraEvent) and event.name == 'review':
|
||||||
@@ -328,6 +375,10 @@ class ReviewState(State):
|
|||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
|
||||||
|
return 'ReviewState'
|
||||||
|
|
||||||
def handleEvent(self, event, context):
|
def handleEvent(self, event, context):
|
||||||
|
|
||||||
if isinstance(event, GuiEvent) and event.name == 'postprocess':
|
if isinstance(event, GuiEvent) and event.name == 'postprocess':
|
||||||
@@ -342,6 +393,10 @@ class PostprocessState(State):
|
|||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
|
||||||
|
return 'PostprocessState'
|
||||||
|
|
||||||
def handleEvent(self, event, context):
|
def handleEvent(self, event, context):
|
||||||
|
|
||||||
if ((isinstance(event, GuiEvent) or isinstance(event, GpioEvent)) and
|
if ((isinstance(event, GuiEvent) or isinstance(event, GpioEvent)) and
|
||||||
|
|||||||
59
photobooth/Threading.py
Normal file
59
photobooth/Threading.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Photobooth - a flexible photo booth software
|
||||||
|
# Copyright (C) 2018 Balthasar Reuter <photobooth at re - web dot eu>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from enum import IntEnum
|
||||||
|
from multiprocessing import Queue
|
||||||
|
|
||||||
|
|
||||||
|
class Communicator:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self._queues = [Queue() for _ in Workers]
|
||||||
|
|
||||||
|
def send(self, target, message):
|
||||||
|
|
||||||
|
if not isinstance(target, Workers):
|
||||||
|
raise TypeError('target must be a member of Workers')
|
||||||
|
|
||||||
|
self._queues[target].put(message)
|
||||||
|
|
||||||
|
def recv(self, worker, block=True):
|
||||||
|
|
||||||
|
if not isinstance(worker, Workers):
|
||||||
|
raise TypeError('worker must be a member of Workers')
|
||||||
|
|
||||||
|
return self._queues[worker].get(block)
|
||||||
|
|
||||||
|
def iter(self, worker):
|
||||||
|
|
||||||
|
if not isinstance(worker, Workers):
|
||||||
|
raise TypeError('worker must be a member of Workers')
|
||||||
|
|
||||||
|
return iter(self._queues[worker].get, None)
|
||||||
|
|
||||||
|
|
||||||
|
class Workers(IntEnum):
|
||||||
|
MASTER = 0
|
||||||
|
GUI = 1
|
||||||
|
CAMERA = 2
|
||||||
|
GPIO = 3
|
||||||
|
WORKER = 4
|
||||||
@@ -19,12 +19,15 @@
|
|||||||
|
|
||||||
from . import GuiState
|
from . import GuiState
|
||||||
|
|
||||||
|
from .. import StateMachine
|
||||||
|
|
||||||
|
|
||||||
class GuiSkeleton:
|
class GuiSkeleton:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, communicator):
|
||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
self._comm = communicator
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def idle(self):
|
def idle(self):
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ from PyQt5 import QtWidgets
|
|||||||
|
|
||||||
from PIL import ImageQt
|
from PIL import ImageQt
|
||||||
|
|
||||||
|
from ... import StateMachine
|
||||||
|
from ...Threading import Workers
|
||||||
|
|
||||||
from .. import GuiState
|
from .. import GuiState
|
||||||
from ..GuiSkeleton import GuiSkeleton
|
from ..GuiSkeleton import GuiSkeleton
|
||||||
from ..GuiPostprocessor import GuiPostprocessor
|
from ..GuiPostprocessor import GuiPostprocessor
|
||||||
@@ -38,9 +41,9 @@ from . import Receiver
|
|||||||
|
|
||||||
class PyQt5Gui(GuiSkeleton):
|
class PyQt5Gui(GuiSkeleton):
|
||||||
|
|
||||||
def __init__(self, argv, config, camera_conn, worker_queue):
|
def __init__(self, argv, config, camera_conn, worker_queue, communicator):
|
||||||
|
|
||||||
super().__init__()
|
super().__init__(communicator)
|
||||||
|
|
||||||
self._cfg = config
|
self._cfg = config
|
||||||
self._conn = camera_conn
|
self._conn = camera_conn
|
||||||
@@ -182,6 +185,8 @@ class PyQt5Gui(GuiSkeleton):
|
|||||||
|
|
||||||
def _showSettings(self):
|
def _showSettings(self):
|
||||||
|
|
||||||
|
self._comm.send(Workers.MASTER, StateMachine.GuiEvent('settings'))
|
||||||
|
|
||||||
self._disableTrigger()
|
self._disableTrigger()
|
||||||
self._disableEscape()
|
self._disableEscape()
|
||||||
self._lastHandle = self._showSettings
|
self._lastHandle = self._showSettings
|
||||||
@@ -190,6 +195,8 @@ class PyQt5Gui(GuiSkeleton):
|
|||||||
|
|
||||||
def _showStart(self, state):
|
def _showStart(self, state):
|
||||||
|
|
||||||
|
self._comm.send(Workers.MASTER, StateMachine.GuiEvent('start'))
|
||||||
|
|
||||||
self._disableTrigger()
|
self._disableTrigger()
|
||||||
self._enableEscape()
|
self._enableEscape()
|
||||||
self._lastHandle = self._showWelcomeScreen
|
self._lastHandle = self._showWelcomeScreen
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ from . import camera, gui
|
|||||||
from .Config import Config
|
from .Config import Config
|
||||||
from .Photobooth import Photobooth
|
from .Photobooth import Photobooth
|
||||||
from .util import lookup_and_import
|
from .util import lookup_and_import
|
||||||
|
from .StateMachine import Context
|
||||||
|
from .Threading import Communicator, Workers
|
||||||
from .Worker import Worker
|
from .Worker import Worker
|
||||||
|
|
||||||
|
|
||||||
@@ -99,7 +101,7 @@ class WorkerProcess(mp.Process):
|
|||||||
|
|
||||||
class GuiProcess(mp.Process):
|
class GuiProcess(mp.Process):
|
||||||
|
|
||||||
def __init__(self, argv, config, conn, queue):
|
def __init__(self, argv, config, conn, queue, communicator):
|
||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
@@ -107,12 +109,13 @@ class GuiProcess(mp.Process):
|
|||||||
self.cfg = config
|
self.cfg = config
|
||||||
self.conn = conn
|
self.conn = conn
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
|
self.comm = communicator
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
||||||
Gui = lookup_and_import(gui.modules, self.cfg.get('Gui', 'module'),
|
Gui = lookup_and_import(gui.modules, self.cfg.get('Gui', 'module'),
|
||||||
'gui')
|
'gui')
|
||||||
sys.exit(Gui(self.argv, self.cfg, self.conn, self.queue).run())
|
sys.exit(Gui(self.argv, self.cfg, self.conn, self.queue, self.comm).run())
|
||||||
|
|
||||||
|
|
||||||
def run(argv):
|
def run(argv):
|
||||||
@@ -122,6 +125,9 @@ def run(argv):
|
|||||||
# Load configuration
|
# Load configuration
|
||||||
config = Config('photobooth.cfg')
|
config = Config('photobooth.cfg')
|
||||||
|
|
||||||
|
comm = Communicator()
|
||||||
|
context = Context()
|
||||||
|
|
||||||
# Create communication objects:
|
# Create communication objects:
|
||||||
# 1. We use a pipe to connect GUI and camera process
|
# 1. We use a pipe to connect GUI and camera process
|
||||||
# 2. We use a queue to feed tasks to the postprocessing process
|
# 2. We use a queue to feed tasks to the postprocessing process
|
||||||
@@ -138,9 +144,12 @@ def run(argv):
|
|||||||
worker_proc = WorkerProcess(config, worker_queue)
|
worker_proc = WorkerProcess(config, worker_queue)
|
||||||
worker_proc.start()
|
worker_proc.start()
|
||||||
|
|
||||||
gui_proc = GuiProcess(argv, config, gui_conn, worker_queue)
|
gui_proc = GuiProcess(argv, config, gui_conn, worker_queue, comm)
|
||||||
gui_proc.start()
|
gui_proc.start()
|
||||||
|
|
||||||
|
for event in comm.iter(Workers.MASTER):
|
||||||
|
context.handleEvent(event)
|
||||||
|
|
||||||
# Close endpoints
|
# Close endpoints
|
||||||
gui_conn.close()
|
gui_conn.close()
|
||||||
camera_conn.close()
|
camera_conn.close()
|
||||||
@@ -156,7 +165,7 @@ def run(argv):
|
|||||||
def main(argv):
|
def main(argv):
|
||||||
|
|
||||||
# Setup log level and format
|
# Setup log level and format
|
||||||
log_level = logging.INFO
|
log_level = logging.DEBUG
|
||||||
formatter = logging.Formatter(
|
formatter = logging.Formatter(
|
||||||
'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user