diff --git a/photobooth/Gpio.py b/photobooth/Gpio.py deleted file mode 100644 index 7bc1c2d..0000000 --- a/photobooth/Gpio.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -# Photobooth - a flexible photo booth software -# Copyright (C) 2018 Balthasar Reuter -# -# 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 . - -from gpiozero import LED, Button - - -class Gpio: - - def __init__(self): - - self._buttons = [] - self._lamps = [] - - def setButton(self, bcm_pin, handler): - - self._buttons.append(Button(bcm_pin)) - self._buttons[-1].when_pressed = handler - - def setLamp(self, bcm_pin): - - self._lamps.append(LED(bcm_pin)) - return len(self._lamps) - 1 - - def lampOn(self, index): - - self._lamps[index].on() - - def lampOff(self, index): - - self._lamps[index].off() - - def lampToggle(self, index): - - self._lamps[index].toggle() diff --git a/photobooth/gpio/__init__.py b/photobooth/gpio/__init__.py new file mode 100644 index 0000000..d0f4525 --- /dev/null +++ b/photobooth/gpio/__init__.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Photobooth - a flexible photo booth software +# Copyright (C) 2018 Balthasar Reuter +# +# 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 . + +import logging + +from .. import StateMachine +from ..Threading import Workers + + +class Gpio: + + def __init__(self, config, comm): + + super().__init__() + + self._comm = comm + self._gpio = None + + self._is_trigger = False + self._is_enabled = config.getBool('Gpio', 'enable') + + self.initGpio(config) + + def initGpio(self, config): + + if self._is_enabled: + self._gpio = Entities() + + lamp_pin = config.getInt('Gpio', 'lamp_pin') + trigger_pin = config.getInt('Gpio', 'trigger_pin') + exit_pin = config.getInt('Gpio', 'exit_pin') + + logging.info(('GPIO enabled (lamp_pin=%d, trigger_pin=%d, ' + 'exit_pin=%d)'), lamp_pin, trigger_pin, exit_pin) + + self._gpio.setButton(trigger_pin, self.trigger) + self._gpio.setButton(exit_pin, self.exit) + self._lamp = self._gpio.setLamp(lamp_pin) + else: + logging.info('GPIO disabled') + + def run(self): + + for state in self._comm.iter(Workers.GPIO): + self.handleState(state) + + return True + + def handleState(self, state): + + if isinstance(state, StateMachine.IdleState): + self.showIdle() + elif isinstance(state, StateMachine.GreeterState): + self.showGreeter() + elif isinstance(state, StateMachine.CountdownState): + self.showCountdown() + elif isinstance(state, StateMachine.CaptureState): + self.showCapture() + elif isinstance(state, StateMachine.AssembleState): + self.showAssemble() + elif isinstance(state, StateMachine.ReviewState): + self.showReview() + elif isinstance(state, StateMachine.PostprocessState): + self.showPostprocess() + elif isinstance(state, StateMachine.TeardownState): + self.teardown(state) + + def teardown(self, state): + + pass + + def enableTrigger(self): + + if self._is_enabled: + self._is_trigger = True + self._gpio.lampOn(self._lamp) + + def disableTrigger(self): + + if self._is_enabled: + self._is_trigger = False + self._gpio.lampOff(self._lamp) + + def trigger(self): + + if self._is_trigger: + self.disableTrigger() + self._comm.send(Workers.MASTER, StateMachine.GpioEvent('trigger')) + + def exit(self): + + self._comm.send( + Workers.Master, + StateMachine.TeardownEvent(StateMachine.TeardownEvent.WELCOME)) + + def showIdle(self): + + self.enableTrigger() + + def showGreeter(self): + + self.disableTrigger() + + def showCountdown(self): + + pass + + def showCapture(self): + + pass + + def showAssemble(self): + + pass + + def showReview(self): + + pass + + def showPostprocess(self): + + pass + + +class Entities: + + def __init__(self): + + super().__init__() + + import gpiozero + self.LED = gpiozero.LED + self.Button = gpiozero.Button + + self._buttons = [] + self._lamps = [] + + def setButton(self, bcm_pin, handler): + + self._buttons.append(self.Button(bcm_pin)) + self._buttons[-1].when_pressed = handler + + def setLamp(self, bcm_pin): + + self._lamps.append(self.LED(bcm_pin)) + return len(self._lamps) - 1 + + def lampOn(self, index): + + self._lamps[index].on() + + def lampOff(self, index): + + self._lamps[index].off() + + def lampToggle(self, index): + + self._lamps[index].toggle() diff --git a/photobooth/main.py b/photobooth/main.py index 06d6fe5..44c2d18 100644 --- a/photobooth/main.py +++ b/photobooth/main.py @@ -30,6 +30,7 @@ import sys from . import camera, gui from .Config import Config +from .gpio import Gpio from .util import lookup_and_import from .StateMachine import Context, ErrorEvent from .Threading import Communicator, Workers @@ -38,7 +39,7 @@ from .worker import Worker class CameraProcess(mp.Process): - def __init__(self, config, comm): + def __init__(self, argv, config, comm): super().__init__() self.daemon = True @@ -60,41 +61,61 @@ class CameraProcess(mp.Process): self._comm.send(Workers.MASTER, ErrorEvent('Camera', str(e))) -class WorkerProcess(mp.Process): - - def __init__(self, config, comm): - - super().__init__() - self.daemon = True - - self.cfg = config - self.comm = comm - - def run(self): - - while True: - try: - if Worker(self.cfg, self.comm).run(): - break - except Exception as e: - self._comm.send(Workers.MASTER, ErrorEvent('Worker', str(e))) - - class GuiProcess(mp.Process): def __init__(self, argv, config, communicator): super().__init__() - self.argv = argv - self.cfg = config - self.comm = communicator + self._argv = argv + self._cfg = config + self._comm = communicator 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') - sys.exit(Gui(self.argv, self.cfg, self.comm).run()) + return Gui(self._argv, self._cfg, self._comm).run() + + +class WorkerProcess(mp.Process): + + def __init__(self, argv, config, comm): + + super().__init__() + self.daemon = True + + self._cfg = config + self._comm = comm + + def run(self): + + while True: + try: + if Worker(self._cfg, self._comm).run(): + break + except Exception as e: + self._comm.send(Workers.MASTER, ErrorEvent('Worker', str(e))) + + +class GpioProcess(mp.Process): + + def __init__(self, argv, config, comm): + + super().__init__() + self.daemon = True + + self._cfg = config + self._comm = comm + + def run(self): + + while True: + try: + if Gpio(self._cfg, self._comm).run(): + break + except Exception as e: + self.comm.send(Workers.MASTER, ErrorEvent('Gpio', str(e))) def run(argv): @@ -107,29 +128,28 @@ def run(argv): comm = Communicator() context = Context(comm) - # Initialize processes: We use four processes here: - # 1. Camera processing - # 2. Postprocessing + # Initialize processes: We use five processes here: + # 1. Master that collects events and distributes state changes + # 2. Camera handling # 3. GUI - # 4. Master - camera_proc = CameraProcess(config, comm) # camera_conn, worker_queue) - camera_proc.start() + # 4. Postprocessing worker + # 5. GPIO handler + proc_classes = (CameraProcess, WorkerProcess, GuiProcess, GpioProcess) + procs = [P(argv, config, comm) for P in proc_classes] - worker_proc = WorkerProcess(config, comm) - worker_proc.start() - - gui_proc = GuiProcess(argv, config, comm) - gui_proc.start() + for proc in procs: + proc.start() + # Enter main loop for event in comm.iter(Workers.MASTER): exit_code = context.handleEvent(event) if exit_code in (0, 123): break # Wait for processes to finish - gui_proc.join() - worker_proc.join() - camera_proc.join() + for proc in procs: + proc.join() + logging.debug('All processes joined, returning code {}'. format(exit_code)) return exit_code