Beispiel #1
0
class PiApplication(object):

    def __init__(self, config):
        self.config = config

        # Clean directory where pictures are saved
        self.savedir = config.getpath('GENERAL', 'directory')
        if not osp.isdir(self.savedir):
            os.makedirs(self.savedir)
        if osp.isdir(self.savedir) and config.getboolean('GENERAL', 'clear_on_startup'):
            shutil.rmtree(self.savedir)
            os.makedirs(self.savedir)

        # Prepare GPIO, physical pins mode
        GPIO.setmode(GPIO.BOARD)

        # Prepare the pygame module for use
        os.environ['SDL_VIDEO_CENTERED'] = '1'
        pygame.init()
        # Dont catch mouse motion to avoid filling the queue during long actions
        pygame.event.set_blocked(pygame.MOUSEMOTION)

        # Create window of (width, height)
        self.window = PtbWindow('Pibooth', config.gettyped('WINDOW', 'size'))

        self.state_machine = StateMachine(self)
        self.state_machine.add_state(StateWait())
        self.state_machine.add_state(StateChoose(30))  # 30s before going back to the start
        self.state_machine.add_state(StateChosen(4))
        self.state_machine.add_state(StateCapture())
        self.state_machine.add_state(StateProcessing())
        self.state_machine.add_state(StatePrint())
        self.state_machine.add_state(StateFinish(0.5))
        if config.getboolean('GENERAL', 'failsafe'):
            self.state_machine.add_failsafe_state(StateFailSafe(2))

        # Initialize the camera
        if camera.gp_camera_connected() and camera.rpi_camera_connected():
            cam_class = camera.HybridCamera
        elif camera.gp_camera_connected():
            cam_class = camera.GpCamera
        elif camera.rpi_camera_connected():
            cam_class = camera.RpiCamera
        else:
            raise EnvironmentError("Neither PiCamera nor GPhoto2 camera detected")

        self.camera = cam_class(config.getint('CAMERA', 'iso'),
                                config.gettyped('CAMERA', 'resolution'),
                                config.getint('CAMERA', 'rotation'),
                                config.getboolean('CAMERA', 'flip'))

        self.led_picture = PtbLed(config.getint('CONTROLS', 'picture_led_pin'))
        self.button_picture = PtbButton(config.getint('CONTROLS', 'picture_btn_pin'),
                                        config.getfloat('CONTROLS', 'debounce_delay'))

        self.led_print = PtbLed(config.getint('CONTROLS', 'print_led_pin'))
        self.button_print = PtbButton(config.getint('CONTROLS', 'print_btn_pin'),
                                      config.getfloat('CONTROLS', 'debounce_delay'))

        self.led_startup = PtbLed(config.getint('CONTROLS', 'startup_led_pin'))
        self.led_preview = PtbLed(config.getint('CONTROLS', 'preview_led_pin'))

        self.printer = PtbPrinter(config.get('PRINTER', 'printer_name'))

        # Variables shared between states
        self.dirname = None
        self.nbr_captures = None
        self.nbr_printed = 0
        self.previous_picture = None
        self.previous_picture_file = None

        self.capture_choices = config.gettyped('PICTURE', 'captures')
        if isinstance(self.capture_choices, int):
            self.capture_choices = (self.capture_choices,)
        for chx in self.capture_choices:
            if chx not in [1, 2, 3, 4]:
                raise ValueError("Invalid captures number '{}'".format(chx))

    def find_quit_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if event.type == pygame.QUIT or\
                    (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):
                return event
        return None

    def find_fullscreen_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if  event.type == pygame.KEYDOWN and\
                    event.key == pygame.K_f and pygame.key.get_mods() & pygame.KMOD_CTRL:
                return event
        return None

    def find_resize_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if event.type == pygame.VIDEORESIZE:
                return event
        return None

    def find_picture_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if (event.type == pygame.KEYDOWN and event.key == pygame.K_p) or \
                    (event.type == BUTTON_DOWN and event.pin == self.button_picture):
                return event
            elif event.type == pygame.MOUSEBUTTONUP:
                rect = self.window.get_rect()
                if pygame.Rect(0, 0, rect.width // 2, rect.height).collidepoint(event.pos):
                    return event
        return None

    def find_print_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if (event.type == pygame.KEYDOWN and event.key == pygame.K_e and
                    pygame.key.get_mods() & pygame.KMOD_CTRL) or \
                    (event.type == BUTTON_DOWN and event.pin == self.button_print):
                return event
            elif event.type == pygame.MOUSEBUTTONUP:
                rect = self.window.get_rect()
                if pygame.Rect(rect.width // 2, 0, rect.width // 2, rect.height).collidepoint(event.pos):
                    return event
        return None

    def find_choice_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if (event.type == pygame.KEYDOWN and event.key == pygame.K_LEFT) or \
                    (event.type == BUTTON_DOWN and event.pin == self.button_picture):
                event.key = pygame.K_LEFT
                return event
            elif (event.type == pygame.KEYDOWN and event.key == pygame.K_RIGHT) or \
                    (event.type == BUTTON_DOWN and event.pin == self.button_print):
                event.key = pygame.K_RIGHT
                return event
            elif event.type == pygame.MOUSEBUTTONUP:
                rect = self.window.get_rect()
                if pygame.Rect(0, 0, rect.width // 2, rect.height).collidepoint(event.pos):
                    event.key = pygame.K_LEFT
                else:
                    event.key = pygame.K_RIGHT
                return event
        return None

    def main_loop(self):
        """Run the main game loop.
        """
        try:
            self.led_startup.switch_on()
            self.state_machine.set_state('wait')
            clock = pygame.time.Clock()

            while True:
                events = list(reversed(pygame.event.get()))  # Take all events, most recent first

                if self.find_quit_event(events):
                    break

                if self.find_fullscreen_event(events):
                    self.window.toggle_fullscreen()

                event = self.find_resize_event(events)
                if event:
                    self.window.resize(event.size)

                self.state_machine.process(events)
                clock.tick(40)  # Ensure the program will never run at more than x frames per second

        finally:
            self.led_startup.quit()
            self.led_preview.quit()
            self.led_picture.quit()
            self.led_print.quit()
            GPIO.cleanup()
            self.camera.quit()
            self.printer.quit()
            pygame.quit()
Beispiel #2
0
class PiApplication(object):
    def __init__(self, config, plugin_manager):
        self._pm = plugin_manager
        self._config = config

        # Create directories where pictures are saved
        for savedir in config.gettuple('GENERAL', 'directory', 'path'):
            if osp.isdir(savedir) and config.getboolean('GENERAL', 'debug'):
                shutil.rmtree(savedir)
            if not osp.isdir(savedir):
                os.makedirs(savedir)

        # Prepare the pygame module for use
        os.environ['SDL_VIDEO_CENTERED'] = '1'
        pygame.init()

        # Create window of (width, height)
        init_size = self._config.gettyped('WINDOW', 'size')
        init_debug = self._config.getboolean('GENERAL', 'debug')
        init_color = self._config.gettyped('WINDOW', 'background')
        init_text_color = self._config.gettyped('WINDOW', 'text_color')
        if not isinstance(init_color, (tuple, list)):
            init_color = self._config.getpath('WINDOW', 'background')

        title = 'Pibooth v{}'.format(pibooth.__version__)
        if not isinstance(init_size, str):
            self._window = PtbWindow(title,
                                     init_size,
                                     color=init_color,
                                     text_color=init_text_color,
                                     debug=init_debug)
        else:
            self._window = PtbWindow(title,
                                     color=init_color,
                                     text_color=init_text_color,
                                     debug=init_debug)

        self._menu = None
        self._multipress_timer = PoolingTimer(
            config.getfloat('CONTROLS', 'multi_press_delay'), False)

        # Define states of the application
        self._machine = StateMachine(self._pm, self._config, self,
                                     self._window)
        self._machine.add_state('wait')
        self._machine.add_state('choose')
        self._machine.add_state('chosen')
        self._machine.add_state('preview')
        self._machine.add_state('capture')
        self._machine.add_state('processing')
        self._machine.add_state('filter')
        self._machine.add_state('print')
        self._machine.add_state('finish')

        # ---------------------------------------------------------------------
        # Variables shared with plugins
        # Change them may break plugins compatibility
        self.capture_nbr = None
        self.capture_date = None
        self.capture_choices = (4, 1)
        self.previous_picture = None
        self.previous_animated = None
        self.previous_picture_file = None

        self.count = Counters(self._config.join_path("counters.pickle"),
                              taken=0,
                              printed=0,
                              forgotten=0,
                              remaining_duplicates=self._config.getint(
                                  'PRINTER', 'max_duplicates'))

        self.camera = camera.get_camera(
            config.getint('CAMERA', 'iso'),
            config.gettyped('CAMERA', 'resolution'),
            config.getint('CAMERA', 'rotation'),
            config.getboolean('CAMERA', 'flip'),
            config.getboolean('CAMERA', 'delete_internal_memory'))

        self.buttons = ButtonBoard(
            capture="BOARD" + config.get('CONTROLS', 'picture_btn_pin'),
            printer="BOARD" + config.get('CONTROLS', 'print_btn_pin'),
            hold_time=config.getfloat('CONTROLS', 'debounce_delay'),
            pull_up=True)
        self.buttons.capture.when_held = self._on_button_capture_held
        self.buttons.printer.when_held = self._on_button_printer_held

        self.leds = LEDBoard(
            capture="BOARD" + config.get('CONTROLS', 'picture_led_pin'),
            printer="BOARD" + config.get('CONTROLS', 'print_led_pin'))

        self.printer = Printer(config.get('PRINTER', 'printer_name'),
                               config.getint('PRINTER', 'max_pages'),
                               self.count)
        # ---------------------------------------------------------------------

    def _initialize(self):
        """Restore the application with initial parameters defined in the
        configuration file.
        Only parameters that can be changed at runtime are restored.
        """
        # Handle the language configuration
        language.CURRENT = self._config.get('GENERAL', 'language')
        fonts.CURRENT = fonts.get_filename(
            self._config.gettuple('PICTURE', 'text_fonts', str)[0])

        # Set the captures choices
        choices = self._config.gettuple('PICTURE', 'captures', int)
        for chx in choices:
            if chx not in [1, 2, 3, 4]:
                LOGGER.warning(
                    "Invalid captures number '%s' in config, fallback to '%s'",
                    chx, self.capture_choices)
                choices = self.capture_choices
                break
        self.capture_choices = choices

        # Handle autostart of the application
        self._config.handle_autostart()

        self._window.arrow_location = self._config.get('WINDOW', 'arrows')
        self._window.arrow_offset = self._config.getint(
            'WINDOW', 'arrows_x_offset')
        self._window.text_color = self._config.gettyped('WINDOW', 'text_color')
        self._window.drop_cache()

        # Handle window size
        size = self._config.gettyped('WINDOW', 'size')
        if isinstance(size, str) and size.lower() == 'fullscreen':
            if not self._window.is_fullscreen:
                self._window.toggle_fullscreen()
        else:
            if self._window.is_fullscreen:
                self._window.toggle_fullscreen()
        self._window.debug = self._config.getboolean('GENERAL', 'debug')

        # Handle debug mode
        if not self._config.getboolean('GENERAL', 'debug'):
            set_logging_level()  # Restore default level
            self._machine.add_failsafe_state('failsafe')
        else:
            set_logging_level(logging.DEBUG)
            self._machine.remove_state('failsafe')

        # Reset the print counter (in case of max_pages is reached)
        self.printer.max_pages = self._config.getint('PRINTER', 'max_pages')

    def _on_button_capture_held(self):
        """Called when the capture button is pressed.
        """
        if all(self.buttons.value):
            self.buttons.capture.hold_repeat = True
            if self._multipress_timer.elapsed() == 0:
                self._multipress_timer.start()
            if self._multipress_timer.is_timeout():
                # Capture was held while printer was pressed
                if self._menu and self._menu.is_shown():
                    # Convert HW button events to keyboard events for menu
                    event = self._menu.create_back_event()
                    LOGGER.debug("BUTTONDOWN: generate MENU-ESC event")
                else:
                    event = pygame.event.Event(BUTTONDOWN,
                                               capture=1,
                                               printer=1,
                                               button=self.buttons)
                    LOGGER.debug("BUTTONDOWN: generate DOUBLE buttons event")
                self.buttons.capture.hold_repeat = False
                self._multipress_timer.reset()
                pygame.event.post(event)
        else:
            # Capture was held but printer not pressed
            if self._menu and self._menu.is_shown():
                # Convert HW button events to keyboard events for menu
                event = self._menu.create_next_event()
                LOGGER.debug("BUTTONDOWN: generate MENU-NEXT event")
            else:
                event = pygame.event.Event(BUTTONDOWN,
                                           capture=1,
                                           printer=0,
                                           button=self.buttons.capture)
                LOGGER.debug("BUTTONDOWN: generate CAPTURE button event")
            self.buttons.capture.hold_repeat = False
            self._multipress_timer.reset()
            pygame.event.post(event)

    def _on_button_printer_held(self):
        """Called when the printer button is pressed.
        """
        if all(self.buttons.value):
            # Printer was held while capture was pressed
            # but don't do anything here, let capture_held handle it instead
            pass
        else:
            # Printer was held but capture not pressed
            if self._menu and self._menu.is_shown():
                # Convert HW button events to keyboard events for menu
                event = self._menu.create_click_event()
                LOGGER.debug("BUTTONDOWN: generate MENU-APPLY event")
            else:
                event = pygame.event.Event(BUTTONDOWN,
                                           capture=0,
                                           printer=1,
                                           button=self.buttons.printer)
                LOGGER.debug("BUTTONDOWN: generate PRINTER event")
            pygame.event.post(event)

    @property
    def picture_filename(self):
        """Return the final picture file name.
        """
        if not self.capture_date:
            raise EnvironmentError(
                "The 'capture_date' attribute is not set yet")
        return "{}_pibooth.jpg".format(self.capture_date)

    def find_quit_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if event.type == pygame.QUIT:
                return event
        return None

    def find_settings_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
                return event
            if event.type == BUTTONDOWN and event.capture and event.printer:
                return event
        return None

    def find_fullscreen_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if event.type == pygame.KEYDOWN and \
                    event.key == pygame.K_f and pygame.key.get_mods() & pygame.KMOD_CTRL:
                return event
        return None

    def find_resize_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if event.type == pygame.VIDEORESIZE:
                return event
        return None

    def find_capture_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if event.type == pygame.KEYDOWN and event.key == pygame.K_p:
                return event
            if event.type == pygame.MOUSEBUTTONUP and event.button in (1, 2,
                                                                       3):
                # Don't consider the mouse wheel (button 4 & 5):
                rect = self._window.get_rect()
                if pygame.Rect(0, 0, rect.width // 2,
                               rect.height).collidepoint(event.pos):
                    return event
            if event.type == BUTTONDOWN and event.capture:
                return event
        return None

    def find_print_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if event.type == pygame.KEYDOWN and event.key == pygame.K_e\
                        and pygame.key.get_mods() & pygame.KMOD_CTRL:
                return event
            if event.type == pygame.MOUSEBUTTONUP and event.button in (1, 2,
                                                                       3):
                # Don't consider the mouse wheel (button 4 & 5):
                rect = self._window.get_rect()
                if pygame.Rect(rect.width // 2, 0, rect.width // 2,
                               rect.height).collidepoint(event.pos):
                    return event
            if event.type == BUTTONDOWN and event.printer:
                return event
        return None

    def find_print_status_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if event.type == PRINTER_TASKS_UPDATED:
                return event
        return None

    def find_choice_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if event.type == pygame.KEYDOWN and event.key == pygame.K_LEFT:
                return event
            if event.type == pygame.KEYDOWN and event.key == pygame.K_RIGHT:
                return event
            if event.type == pygame.MOUSEBUTTONUP and event.button in (1, 2,
                                                                       3):
                # Don't consider the mouse wheel (button 4 & 5):
                print("MOUSEBUTTONUP")
                rect = self._window.get_rect()
                x, y = event.pos
                if pygame.Rect(0, 0, rect.width // 2,
                               rect.height).collidepoint(event.pos):
                    event.key = pygame.K_LEFT
                else:
                    event.key = pygame.K_RIGHT
                return event
            if event.type == BUTTONDOWN:
                if event.capture:
                    event.key = pygame.K_LEFT
                else:
                    event.key = pygame.K_RIGHT
                return event
        return None

    def main_loop(self):
        """Run the main game loop.
        """
        try:
            fps = 40
            clock = pygame.time.Clock()
            self._initialize()
            self._pm.hook.pibooth_startup(cfg=self._config, app=self)
            self._machine.set_state('wait')

            while True:
                events = list(pygame.event.get())

                if self.find_quit_event(events):
                    break

                if self.find_fullscreen_event(events):
                    self._window.toggle_fullscreen()

                event = self.find_resize_event(events)
                if event:
                    self._window.resize(event.size)

                if not self._menu and self.find_settings_event(events):
                    self.camera.stop_preview()
                    self.leds.off()
                    self._menu = PiConfigMenu(self._window, self._config,
                                              self.count)
                    self._menu.show()
                    self.leds.blink(on_time=0.1, off_time=1)
                elif self._menu and self._menu.is_shown():
                    self._menu.process(events)
                elif self._menu and not self._menu.is_shown():
                    self.leds.off()
                    self._initialize()
                    self._machine.set_state('wait')
                    self._menu = None
                else:
                    self._machine.process(events)

                pygame.display.update()
                clock.tick(
                    fps
                )  # Ensure the program will never run at more than <fps> frames per second

        except Exception as ex:
            LOGGER.error(str(ex), exc_info=True)
            LOGGER.error(get_crash_message())
        finally:
            self._pm.hook.pibooth_cleanup(app=self)
            pygame.quit()
Beispiel #3
0
class PiApplication(object):
    def __init__(self, config):
        self._config = config

        # Clean directory where pictures are saved
        savedir = config.getpath('GENERAL', 'directory')
        if not osp.isdir(savedir):
            os.makedirs(savedir)
        elif osp.isdir(savedir) and config.getboolean('GENERAL', 'debug'):
            shutil.rmtree(savedir)
            os.makedirs(savedir)

        # Prepare the pygame module for use
        os.environ['SDL_VIDEO_CENTERED'] = '1'
        pygame.init()
        # Dont catch mouse motion to avoid filling the queue during long actions
        pygame.event.set_blocked(pygame.MOUSEMOTION)

        # Create window of (width, height)
        init_size = self._config.gettyped('WINDOW', 'size')
        init_debug = self._config.getboolean('GENERAL', 'debug')
        init_color = self._config.gettyped('WINDOW', 'background')
        init_text_color = self._config.gettyped('WINDOW', 'text_color')
        if not isinstance(init_color, (tuple, list)):
            init_color = self._config.getpath('WINDOW', 'background')

        title = 'Pibooth v{}'.format(pibooth.__version__)
        if not isinstance(init_size, str):
            self._window = PtbWindow(title,
                                     init_size,
                                     color=init_color,
                                     text_color=init_text_color,
                                     debug=init_debug)
        else:
            self._window = PtbWindow(title,
                                     color=init_color,
                                     text_color=init_text_color,
                                     debug=init_debug)

        # Create plugin manager and defined hooks specification
        self._plugin_manager = pluggy.PluginManager(
            hookspecs.hookspec.project_name)
        self._plugin_manager.add_hookspecs(hookspecs)
        self._plugin_manager.load_setuptools_entrypoints(
            hookspecs.hookspec.project_name)

        # Register plugins
        custom_paths = [
            p for p in self._config.gettuple('GENERAL', 'plugins', 'path') if p
        ]
        load_plugins(self._plugin_manager, *custom_paths)

        # Define states of the application
        self._machine = StateMachine(self._plugin_manager, self._config, self,
                                     self._window)
        self._machine.add_state('wait')
        self._machine.add_state('choose')
        self._machine.add_state('chosen')
        self._machine.add_state('preview')
        self._machine.add_state('capture')
        self._machine.add_state('processing')
        self._machine.add_state('print')
        self._machine.add_state('finish')

        # ---------------------------------------------------------------------
        # Variables shared with plugins
        # Change them may break plugins compatibility
        self.dirname = None
        self.capture_nbr = None
        self.capture_choices = (4, 1)
        self.nbr_duplicates = 0
        self.previous_picture = None
        self.previous_animated = None
        self.previous_picture_file = None

        self.camera = camera.get_camera(
            config.getint('CAMERA', 'iso'),
            config.gettyped('CAMERA', 'resolution'),
            config.getint('CAMERA', 'rotation'),
            config.getboolean('CAMERA', 'flip'),
            config.getboolean('CAMERA', 'delete_internal_memory'))

        self.button_capture = Button(
            "BOARD" + config.get('CONTROLS', 'picture_btn_pin'),
            bounce_time=config.getfloat('CONTROLS', 'debounce_delay'),
            pull_up=True,
            hold_time=1)

        self.button_print = Button(
            "BOARD" + config.get('CONTROLS', 'print_btn_pin'),
            bounce_time=config.getfloat('CONTROLS', 'debounce_delay'),
            pull_up=True,
            hold_time=1)

        self.leds = LEDBoard(
            capture="BOARD" + config.get('CONTROLS', 'picture_led_pin'),
            printer="BOARD" + config.get('CONTROLS', 'print_led_pin'),
            preview="BOARD" + config.get('CONTROLS', 'preview_led_pin'),
            start="BOARD" + config.get('CONTROLS', 'startup_led_pin'))

        self.printer = PtbPrinter(config.get('PRINTER', 'printer_name'))
        # ---------------------------------------------------------------------

    def _initialize(self):
        """Restore the application with initial parameters defined in the
        configuration file.
        Only parameters that can be changed at runtime are restored.
        """
        # Handle the language configuration
        language.CURRENT = self._config.get('GENERAL', 'language')
        fonts.CURRENT = fonts.get_filename(
            self._config.gettuple('PICTURE', 'text_fonts', str)[0])

        # Set the captures choices
        choices = self._config.gettuple('PICTURE', 'captures', int)
        for chx in choices:
            if chx not in [1, 2, 3, 4]:
                LOGGER.warning(
                    "Invalid captures number '%s' in config, fallback to '%s'",
                    chx, self.capture_choices)
                choices = self.capture_choices
                break
        self.capture_choices = choices

        # Handle autostart of the application
        self._config.enable_autostart(
            self._config.getboolean('GENERAL', 'autostart'))

        self._window.arrow_location = self._config.get('WINDOW', 'arrows')
        self._window.arrow_offset = self._config.getint(
            'WINDOW', 'arrows_x_offset')
        self._window.text_color = self._config.gettyped('WINDOW', 'text_color')
        self._window.drop_cache()

        # Handle window size
        size = self._config.gettyped('WINDOW', 'size')
        if isinstance(size, str) and size.lower() == 'fullscreen':
            if not self._window.is_fullscreen:
                self._window.toggle_fullscreen()
        else:
            if self._window.is_fullscreen:
                self._window.toggle_fullscreen()
        self._window.debug = self._config.getboolean('GENERAL', 'debug')

        # Handle debug mode
        if not self._config.getboolean('GENERAL', 'debug'):
            set_logging_level()  # Restore default level
            self._machine.add_failsafe_state('failsafe')
        else:
            set_logging_level(logging.DEBUG)
            self._machine.remove_state('failsafe')

        # Initialize state machine
        self._machine.set_state('wait')

    @property
    def printer_unavailable(self):
        """Return True is paper/ink counter is reached or printing is disabled
        """
        if self._config.getint('PRINTER', 'max_pages') < 0:  # No limit
            return False
        return self.printer.nbr_printed >= self._config.getint(
            'PRINTER', 'max_pages')

    def find_quit_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if event.type == pygame.QUIT:
                return event
        return None

    def find_settings_event(self, events, type_filter=None):
        """Return the first found event if found in the list.
        """
        for event in events:
            if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE and \
                    (type_filter is None or type_filter == event.type):
                return event
            if (type_filter is None or type_filter == BUTTON_DOWN) and\
                    self.button_capture.is_pressed and self.button_print.is_pressed:
                return event
        return None

    def find_fullscreen_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if event.type == pygame.KEYDOWN and \
                    event.key == pygame.K_f and pygame.key.get_mods() & pygame.KMOD_CTRL:
                return event
        return None

    def find_resize_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if event.type == pygame.VIDEORESIZE:
                return event
        return None

    def find_capture_event(self, events, type_filter=None):
        """Return the first found event if found in the list.
        """
        for event in events:
            if (event.type == pygame.KEYDOWN and event.key == pygame.K_p) and \
                    (type_filter is None or type_filter == event.type):
                return event
            elif (type_filter is None or type_filter == BUTTON_DOWN) and\
                    self.button_capture.is_pressed:
                return event
            elif event.type == pygame.MOUSEBUTTONUP:
                rect = self._window.get_rect()
                if pygame.Rect(0, 0, rect.width // 2,
                               rect.height).collidepoint(event.pos):
                    if type_filter is None or type_filter == event.type:
                        return event
        return None

    def find_print_event(self, events, type_filter=None):
        """Return the first found event if found in the list.
        """
        for event in events:
            if (event.type == pygame.KEYDOWN and event.key == pygame.K_e and
                    pygame.key.get_mods() & pygame.KMOD_CTRL) and \
                    (type_filter is None or type_filter == event.type):
                return event
            elif (type_filter is None or type_filter == BUTTON_DOWN) and\
                    self.button_print.is_pressed:
                return event
            elif event.type == pygame.MOUSEBUTTONUP:
                rect = self._window.get_rect()
                if pygame.Rect(rect.width // 2, 0, rect.width // 2,
                               rect.height).collidepoint(event.pos):
                    if type_filter is None or type_filter == event.type:
                        return event
        return None

    def find_print_status_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if event.type == PRINTER_TASKS_UPDATED:
                return event
        return None

    def find_choice_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if (event.type == pygame.KEYDOWN and event.key == pygame.K_LEFT) or \
                    self.button_capture.is_pressed:
                event.key = pygame.K_LEFT
                return event
            elif (event.type == pygame.KEYDOWN and event.key == pygame.K_RIGHT) or \
                    self.button_print.is_pressed:
                event.key = pygame.K_RIGHT
                return event
            elif event.type == pygame.MOUSEBUTTONUP:
                rect = self._window.get_rect()
                if pygame.Rect(0, 0, rect.width // 2,
                               rect.height).collidepoint(event.pos):
                    event.key = pygame.K_LEFT
                else:
                    event.key = pygame.K_RIGHT
                return event
        return None

    def main_loop(self):
        """Run the main game loop.
        """
        try:
            clock = pygame.time.Clock()
            self._initialize()
            self._plugin_manager.hook.pibooth_startup(app=self)
            menu = None
            fps = 40

            while True:
                events = list(pygame.event.get())

                if self.find_quit_event(events):
                    break

                if self.find_fullscreen_event(events):
                    self._window.toggle_fullscreen()

                event = self.find_resize_event(events)
                if event:
                    self._window.resize(event.size)

                if not menu and self.find_settings_event(events):
                    menu = PiConfigMenu(self._window,
                                        self._config,
                                        fps,
                                        version=pibooth.__version__)
                    menu.show()

                if menu and menu.is_shown():
                    # Convert HW button events to keyboard events for menu
                    if self.find_settings_event(events, BUTTON_DOWN):
                        events.insert(0, menu.create_back_event())
                    if self.find_capture_event(events, BUTTON_DOWN):
                        events.insert(0, menu.create_next_event())
                    elif self.find_print_event(events, BUTTON_DOWN):
                        events.insert(0, menu.create_click_event())
                    menu.process(events)
                elif menu and not menu.is_shown():
                    self._initialize()
                    menu = None
                else:
                    self._machine.process(events)

                pygame.display.update()
                clock.tick(
                    fps
                )  # Ensure the program will never run at more than <fps> frames per second

        finally:
            self._plugin_manager.hook.pibooth_cleanup(app=self)
            pygame.quit()
Beispiel #4
0
class PiApplication(object):
    def __init__(self, config):
        self.config = config

        # Clean directory where pictures are saved
        self.savedir = config.getpath('GENERAL', 'directory')
        if not osp.isdir(self.savedir):
            os.makedirs(self.savedir)
        if osp.isdir(self.savedir) and config.getboolean(
                'GENERAL', 'clear_on_startup'):
            shutil.rmtree(self.savedir)
            os.makedirs(self.savedir)

        # Prepare GPIO, physical pins mode
        GPIO.setmode(GPIO.BOARD)

        # Prepare the pygame module for use
        os.environ['SDL_VIDEO_CENTERED'] = '1'
        pygame.init()
        # Dont catch mouse motion to avoid filling the queue during long actions
        pygame.event.set_blocked(pygame.MOUSEMOTION)

        # Create window of (width, height)
        init_size = self.config.gettyped('WINDOW', 'size')
        init_debug = self.config.getboolean('GENERAL', 'debug')
        init_color = self.config.gettyped('WINDOW', 'background')
        init_text_color = self.config.gettyped('WINDOW', 'text_color')
        if not isinstance(init_color, (tuple, list)):
            init_color = self.config.getpath('WINDOW', 'background')
        if not isinstance(init_size, str):
            self.window = PtbWindow('Pibooth',
                                    init_size,
                                    color=init_color,
                                    text_color=init_text_color,
                                    debug=init_debug)
        else:
            self.window = PtbWindow('Pibooth',
                                    color=init_color,
                                    text_color=init_text_color,
                                    debug=init_debug)

        self.state_machine = StateMachine(self)
        self.state_machine.add_state(StateWait())
        self.state_machine.add_state(
            StateChoose(30))  # 30s before going back to the start
        self.state_machine.add_state(StateChosen(4))
        self.state_machine.add_state(StateCapture())
        self.state_machine.add_state(StateProcessing())
        self.state_machine.add_state(StatePrint())
        self.state_machine.add_state(StateFinish(0.5))

        self.camera = camera.get_camera(
            config.getint('CAMERA', 'iso'),
            config.gettyped('CAMERA', 'resolution'),
            config.getint('CAMERA', 'rotation'),
            config.getboolean('CAMERA', 'flip'),
            config.getboolean('CAMERA', 'delete_internal_memory'))

        # Initialize the hardware buttons
        self.led_capture = PtbLed(config.getint('CONTROLS', 'picture_led_pin'))
        self.button_capture = PtbButton(
            config.getint('CONTROLS', 'picture_btn_pin'),
            config.getfloat('CONTROLS', 'debounce_delay'))

        self.led_print = PtbLed(config.getint('CONTROLS', 'print_led_pin'))
        self.button_print = PtbButton(
            config.getint('CONTROLS', 'print_btn_pin'),
            config.getfloat('CONTROLS', 'debounce_delay'))

        self.led_startup = PtbLed(config.getint('CONTROLS', 'startup_led_pin'))
        self.led_preview = PtbLed(config.getint('CONTROLS', 'preview_led_pin'))

        # Initialize the printer
        self.printer = PtbPrinter(config.get('PRINTER', 'printer_name'))

        # Variables shared between states
        self.dirname = None
        self.makers_pool = PicturesMakersPool()
        self.capture_nbr = None
        self.capture_choices = (4, 1)
        self.nbr_duplicates = 0
        self.previous_picture = None
        self.previous_animated = []
        self.previous_picture_file = None

    def initialize(self):
        """Restore the application with initial parameters defined in the
        configuration file.
        Only parameters that can be changed at runtime are restored.
        """
        # Handle the language configuration
        language.CURRENT = self.config.get('GENERAL', 'language')
        fonts.CURRENT = fonts.get_filename(
            self.config.gettuple('PICTURE', 'text_fonts', str)[0])

        # Set the captures choices
        choices = self.config.gettuple('PICTURE', 'captures', int)
        for chx in choices:
            if chx not in [1, 2, 3, 4]:
                LOGGER.warning(
                    "Invalid captures number '%s' in config, fallback to '%s'",
                    chx, self.capture_choices)
                choices = self.capture_choices
                break
        self.capture_choices = choices

        # Reset printed pages number
        self.printer.nbr_printed = 0

        # Handle autostart of the application
        self.config.enable_autostart(
            self.config.getboolean('GENERAL', 'autostart'))

        self.window.arrow_location = self.config.get('WINDOW', 'arrows')
        self.window.arrow_offset = self.config.getint('WINDOW',
                                                      'arrows_x_offset')
        self.window.drop_cache()

        # Handle window size
        size = self.config.gettyped('WINDOW', 'size')
        if isinstance(size, str) and size.lower() == 'fullscreen':
            if not self.window.is_fullscreen:
                self.window.toggle_fullscreen()
        else:
            if self.window.is_fullscreen:
                self.window.toggle_fullscreen()
        self.window.debug = self.config.getboolean('GENERAL', 'debug')

        # Handle debug mode
        if not self.config.getboolean('GENERAL', 'debug'):
            set_logging_level()  # Restore default level
            self.state_machine.add_failsafe_state(StateFailSafe(2))
        else:
            set_logging_level(logging.DEBUG)
            self.state_machine.remove_state('failsafe')

        # Initialize state machine
        self.state_machine.set_state('wait')

    @property
    def printer_unavailable(self):
        """Return True is paper/ink counter is reached or printing is disabled
        """
        if self.config.getint('PRINTER', 'max_pages') < 0:  # No limit
            return False
        return self.printer.nbr_printed >= self.config.getint(
            'PRINTER', 'max_pages')

    def find_quit_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if event.type == pygame.QUIT:
                return event
        return None

    def find_settings_event(self, events, type_filter=None):
        """Return the first found event if found in the list.
        """
        event_capture = None
        event_print = None
        for event in events:
            if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE and \
                    (type_filter is None or type_filter == event.type):
                return event
            if event.type == BUTTON_DOWN:
                if event.pin == self.button_capture and (
                        type_filter is None or type_filter == event.type):
                    event_capture = event
                elif event.pin == self.button_print and (
                        type_filter is None or type_filter == event.type):
                    event_print = event
            if event_capture and event_print:
                return event_capture  # One of both (return != None is enough)

        return None

    def find_fullscreen_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if event.type == pygame.KEYDOWN and \
                    event.key == pygame.K_f and pygame.key.get_mods() & pygame.KMOD_CTRL:
                return event
        return None

    def find_resize_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if event.type == pygame.VIDEORESIZE:
                return event
        return None

    def find_capture_event(self, events, type_filter=None):
        """Return the first found event if found in the list.
        """
        for event in events:
            if (event.type == pygame.KEYDOWN and event.key == pygame.K_p) or \
                    (event.type == BUTTON_DOWN and event.pin == self.button_capture):
                if type_filter is None or type_filter == event.type:
                    return event
            elif event.type == pygame.MOUSEBUTTONUP:
                rect = self.window.get_rect()
                if pygame.Rect(0, 0, rect.width // 2,
                               rect.height).collidepoint(event.pos):
                    if type_filter is None or type_filter == event.type:
                        return event
        return None

    def find_print_event(self, events, type_filter=None):
        """Return the first found event if found in the list.
        """
        for event in events:
            if (event.type == pygame.KEYDOWN and event.key == pygame.K_e and
                    pygame.key.get_mods() & pygame.KMOD_CTRL) or \
                    (event.type == BUTTON_DOWN and event.pin == self.button_print):
                if type_filter is None or type_filter == event.type:
                    return event
            elif event.type == pygame.MOUSEBUTTONUP:
                rect = self.window.get_rect()
                if pygame.Rect(rect.width // 2, 0, rect.width // 2,
                               rect.height).collidepoint(event.pos):
                    if type_filter is None or type_filter == event.type:
                        return event
        return None

    def find_print_status_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if event.type == PRINTER_TASKS_UPDATED:
                return event
        return None

    def find_choice_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if (event.type == pygame.KEYDOWN and event.key == pygame.K_LEFT) or \
                    (event.type == BUTTON_DOWN and event.pin == self.button_capture):
                event.key = pygame.K_LEFT
                return event
            elif (event.type == pygame.KEYDOWN and event.key == pygame.K_RIGHT) or \
                    (event.type == BUTTON_DOWN and event.pin == self.button_print):
                event.key = pygame.K_RIGHT
                return event
            elif event.type == pygame.MOUSEBUTTONUP:
                rect = self.window.get_rect()
                if pygame.Rect(0, 0, rect.width // 2,
                               rect.height).collidepoint(event.pos):
                    event.key = pygame.K_LEFT
                else:
                    event.key = pygame.K_RIGHT
                return event
        return None

    def main_loop(self):
        """Run the main game loop.
        """
        try:
            clock = pygame.time.Clock()
            self.led_startup.switch_on()
            self.initialize()
            menu = None
            fps = 40

            while True:
                events = list(pygame.event.get())

                if self.find_quit_event(events):
                    break

                if self.find_fullscreen_event(events):
                    self.window.toggle_fullscreen()

                event = self.find_resize_event(events)
                if event:
                    self.window.resize(event.size)

                if not menu and self.find_settings_event(events):
                    menu = PiConfigMenu(self.window, self.config, fps)
                    menu.show()

                if menu and menu.is_shown():
                    # Convert HW button events to keyboard events for menu
                    if self.find_settings_event(events, BUTTON_DOWN):
                        events.insert(0, menu.create_back_event())
                    if self.find_capture_event(events, BUTTON_DOWN):
                        events.insert(0, menu.create_next_event())
                    elif self.find_print_event(events, BUTTON_DOWN):
                        events.insert(0, menu.create_click_event())

                    menu.process(events)
                elif menu and not menu.is_shown():
                    self.initialize()
                    menu = None
                else:
                    self.state_machine.process(events)

                pygame.display.update()
                clock.tick(
                    fps
                )  # Ensure the program will never run at more than x frames per second

        finally:
            self.makers_pool.quit()
            self.led_startup.quit()
            self.led_preview.quit()
            self.led_capture.quit()
            self.led_print.quit()
            GPIO.cleanup()
            self.camera.quit()
            self.printer.quit()
            pygame.quit()