class StatePrint(State): def __init__(self): State.__init__(self, 'print', 'finish') self.timer = PoolingTimer( self.app.config.getfloat('PRINTER', 'printer_delay')) self.printed = False def entry_actions(self): self.printed = False if self.timer.timeout == 0: return # Don't show print state with timeit("Display the merged picture"): self.app.window.show_print(self.app.previous_picture) self.app.led_print.blink() self.timer.start() def do_actions(self, events): if self.timer.timeout == 0: return # Don't show print state if self.app.find_print_event( events) and self.app.previous_picture_file: with timeit("Send final picture to printer"): self.app.led_print.switch_on() self.app.printer.print_file(self.app.previous_picture_file) time.sleep(1) self.app.led_print.blink() self.printed = True def validate_transition(self, events): if self.timer.is_timeout() or self.printed: return self.next_name
class StatePrint(State): def __init__(self): State.__init__(self, 'print') self.timer = PoolingTimer( self.app.config.getfloat('PRINTER', 'printer_delay')) self.printed = False def entry_actions(self): self.printed = False with timeit("Display the final picture"): self.app.window.set_print_number( len(self.app.printer.get_all_tasks()), self.app.printer_unavailable) self.app.window.show_print( self.app.previous_picture, self.app.config.getboolean('SERVER', 'show_qr_on_screen'), self.app.previous_picture_qr_file_inverted) self.app.led_print.blink() # Reset timeout in case of settings changed self.timer.timeout = self.app.config.getfloat('PRINTER', 'printer_delay') self.timer.start() def do_actions(self, events): if self.app.find_print_qr_event(events): # print qr code: if self.app.qr_printer.nbr_printed == 0: LOGGER.info("print qr code now") self.app.qr_printer.print_file( self.app.previous_picture_qr_file_print, 1) elif self.app.find_print_event( events) and self.app.previous_print_picture_file: with timeit("Send final picture to printer"): self.app.led_print.switch_on() print_value = self.app.config.get('SERVER', 'print_qr_code') if print_value == "Picture" or print_value == "Picture and Qr Code": self.app.printer.print_file( self.app.previous_print_picture_file, self.app.config.getint('PRINTER', 'pictures_per_page')) if print_value == "Qr Code" or print_value == "Picture and Qr Code": self.app.printer.print_file( self.app.previous_picture_qr_file, self.app.config.getint('PRINTER', 'pictures_per_page')) time.sleep(1) # Just to let the LED switched on self.app.nbr_duplicates += 1 self.app.led_print.blink() self.printed = True def validate_transition(self, events): if self.timer.is_timeout() or self.printed: if self.printed: self.app.window.set_print_number( len(self.app.printer.get_all_tasks()), self.app.printer_unavailable) return 'finish'
class StatePrint(State): def __init__(self): State.__init__(self, 'print') self.timer = PoolingTimer(self.app.config.getfloat('PRINTER', 'printer_delay')) self.printed = False def entry_actions(self): self.printed = False with timeit("Display the merged picture"): self.app.window.show_print(self.app.previous_picture) self.app.led_print.blink() self.timer.start() def do_actions(self, events): if self.app.find_print_event(events) and self.app.previous_picture_file: with timeit("Send final picture to printer"): self.app.led_print.switch_on() self.app.printer.print_file(self.app.previous_picture_file, self.app.config.getint('PRINTER', 'nbr_copies')) time.sleep(2) # Just to let the LED switched on self.app.nbr_printed += 1 self.app.led_print.blink() self.printed = True def validate_transition(self, events): if self.timer.is_timeout() or self.printed: return 'finish'
class StateFinish(State): def __init__(self, timeout): State.__init__(self, 'finish') self.timer = PoolingTimer(timeout) def entry_actions(self): self.app.window.show_finished() self.timer.start() def validate_transition(self, events): if self.timer.is_timeout(): return 'wait'
class StateFailSafe(State): def __init__(self, timeout): State.__init__(self, 'failsafe') self.timer = PoolingTimer(timeout) def entry_actions(self): self.app.dirname = None self.app.nbr_captures = None self.app.nbr_printed = 0 self.app.camera.drop_captures() # Flush previous captures self.app.window.show_oops() self.timer.start() def validate_transition(self, events): if self.timer.is_timeout(): return 'wait'
class StateChosen(State): def __init__(self, timeout): State.__init__(self, 'chosen', 'capture') self.timer = PoolingTimer(timeout) def entry_actions(self): with timeit("Set {} picture(s) mode".format(self.app.max_captures)): self.app.window.show_choice(self.app.max_captures) self.timer.start() def exit_actions(self): self.app.led_picture.switch_off() self.app.led_print.switch_off() def validate_transition(self, events): if self.timer.is_timeout(): return self.next_name
class StateChosen(State): def __init__(self, timeout): State.__init__(self, 'chosen') self.timer = PoolingTimer(timeout) def entry_actions(self): with timeit("Show picture choice ({} pictures selected)".format(self.app.nbr_captures)): self.app.window.show_choice(self.app.capture_choices, selected=self.app.nbr_captures) self.timer.start() def exit_actions(self): self.app.led_picture.switch_off() self.app.led_print.switch_off() def validate_transition(self, events): if self.timer.is_timeout(): return 'capture'
class StateChoose(State): def __init__(self, timeout): State.__init__(self, 'choose', 'chosen') self.timer = PoolingTimer(timeout) def entry_actions(self): with timeit("Show picture choice (no default set)"): self.app.window.show_choice() self.app.max_captures = None self.app.led_picture.blink() self.app.led_print.blink() self.timer.start() def do_actions(self, events): event = self.app.find_choice_event(events) if event: if event.type == pygame.KEYDOWN and event.key == pygame.K_LEFT: self.app.max_captures = self.app.config.getint( 'PICTURE', 'captures') elif event.type == pygame.KEYDOWN and event.key == pygame.K_RIGHT: self.app.max_captures = 1 elif event.pin == self.app.button_picture: self.app.max_captures = self.app.config.getint( 'PICTURE', 'captures') elif event.pin == self.app.button_print: self.app.max_captures = 1 def exit_actions(self): if self.app.max_captures == self.app.config.getint( 'PICTURE', 'captures'): self.app.led_picture.switch_on() self.app.led_print.switch_off() elif self.app.max_captures == 1: self.app.led_print.switch_on() self.app.led_picture.switch_off() else: self.app.led_print.switch_off() self.app.led_picture.switch_off() def validate_transition(self, events): if self.app.max_captures: return self.next_name elif self.timer.is_timeout(): return "wait"
class StatePrint(State): def __init__(self): State.__init__(self, 'print') self.timer = PoolingTimer( self.app.config.getfloat('PRINTER', 'printer_delay')) self.printed = False def entry_actions(self): self.printed = False with timeit("Display the final picture"): self.app.window.set_print_number( len(self.app.printer.get_all_tasks()), self.app.printer_unavailable) self.app.window.show_print(self.app.previous_picture) self.app.led_print.blink() # Reset timeout in case of settings changed self.timer.timeout = self.app.config.getfloat('PRINTER', 'printer_delay') self.timer.start() def do_actions(self, events): if self.app.find_print_event( events) and self.app.previous_picture_file: with timeit("Send final picture to printer"): self.app.led_print.switch_on() self.app.printer.print_file( self.app.previous_picture_file, self.app.config.getint('PRINTER', 'pictures_per_page')) time.sleep(1) # Just to let the LED switched on self.app.nbr_duplicates += 1 self.app.led_print.blink() self.printed = True def validate_transition(self, events): if self.timer.is_timeout() or self.printed: if self.printed: self.app.window.set_print_number( len(self.app.printer.get_all_tasks()), self.app.printer_unavailable) return 'finish'
def preview_countdown(self, timeout, alpha=80): """Show a countdown of `timeout` seconds on the preview. Returns when the countdown is finished. """ timeout = int(timeout) if timeout < 1: raise ValueError("Start time shall be greater than 0") shown = False first_loop = True timer = PoolingTimer(timeout) while not timer.is_timeout(): remaining = int(timer.remaining() + 1) if not self._overlay or remaining != timeout: # Rebluid overlay only if remaining number has changed self._show_overlay(str(remaining), alpha) timeout = remaining shown = False updated_rect = None if self._preview_compatible: updated_rect = self._window.show_image( self._get_preview_image()) elif not shown: updated_rect = self._window.show_image( self._get_preview_image()) shown = True # Do not update dummy preview until next overlay update if first_loop: timer.start( ) # Because first preview capture is longer than others first_loop = False pygame.event.pump() if updated_rect: pygame.display.update(updated_rect) self._show_overlay( LANGUAGES.get(PiConfigParser.language, LANGUAGES['en']).get('smile_message'), alpha) self._window.show_image(self._get_preview_image())
class StateChoose(State): def __init__(self, timeout): State.__init__(self, 'choose') self.timer = PoolingTimer(timeout) def entry_actions(self): with timeit("Show picture choice (nothing selected)"): self.app.window.show_choice(self.app.capt_choices) self.app.nbr_captures = None self.app.led_picture.blink() self.app.led_print.blink() self.timer.start() def do_actions(self, events): event = self.app.find_choice_event(events) if event: if (event.type == pygame.KEYDOWN and event.key == pygame.K_LEFT) \ or (event.type == BUTTON_DOWN and event.pin == self.app.button_picture): self.app.nbr_captures = self.app.capt_choices[0] elif (event.type == pygame.KEYDOWN and event.key == pygame.K_RIGHT) \ or (event.type == BUTTON_DOWN and event.pin == self.app.button_print): self.app.nbr_captures = self.app.capt_choices[1] def exit_actions(self): if self.app.nbr_captures == self.app.capt_choices[0]: self.app.led_picture.switch_on() self.app.led_print.switch_off() elif self.app.nbr_captures == self.app.capt_choices[1]: self.app.led_print.switch_on() self.app.led_picture.switch_off() else: self.app.led_print.switch_off() self.app.led_picture.switch_off() def validate_transition(self, events): if self.app.nbr_captures: return 'chosen' elif self.timer.is_timeout(): return 'wait'
class StateChoose(State): def __init__(self, timeout): State.__init__(self, 'choose') self.timer = PoolingTimer(timeout) def entry_actions(self): with timeit("Show picture choice (nothing selected)"): self.app.window.set_print_number(0) # Hide printer status self.app.window.show_choice(self.app.capture_choices) self.app.capture_nbr = None self.app.led_capture.blink() self.app.led_print.blink() self.timer.start() def do_actions(self, events): event = self.app.find_choice_event(events) if event: if event.key == pygame.K_LEFT: self.app.capture_nbr = self.app.capture_choices[0] elif event.key == pygame.K_RIGHT: self.app.capture_nbr = self.app.capture_choices[1] def exit_actions(self): if self.app.capture_nbr == self.app.capture_choices[0]: self.app.led_capture.switch_on() self.app.led_print.switch_off() elif self.app.capture_nbr == self.app.capture_choices[1]: self.app.led_print.switch_on() self.app.led_capture.switch_off() else: self.app.led_print.switch_off() self.app.led_capture.switch_off() def validate_transition(self, events): if self.app.capture_nbr: return 'chosen' elif self.timer.is_timeout(): return 'wait'
class StateFailSafe(State): def __init__(self, timeout): State.__init__(self, 'failsafe') self.timer = PoolingTimer(timeout) def entry_actions(self): self.app.dirname = None self.app.capture_nbr = None self.app.nbr_duplicates = 0 self.app.previous_animated = [] self.previous_picture = None self.previous_picture_file = None self.previous_print_picture_file = None self.web_upload_sucessful = False self.previous_print_picture_file_qith_qr = None self.previous_picture_qr_file = None self.previous_picture_qr_file_inverted = None self.app.camera.drop_captures() # Flush previous captures self.app.window.show_oops() self.timer.start() def validate_transition(self, events): if self.timer.is_timeout(): return 'wait'
class PicturePlugin(object): """Plugin to build the final picture. """ def __init__(self, plugin_manager): self._pm = plugin_manager self.factory_pool = PicturesFactoryPool() self.picture_destroy_timer = PoolingTimer(0) self.second_previous_picture = None def _reset_vars(self, app): """Destroy final picture (can not be used anymore). """ self.factory_pool.clear() app.previous_picture = None app.previous_animated = None app.previous_picture_file = None @pibooth.hookimpl def pibooth_setup_picture_factory(self, cfg, opt_index, factory): factory.set_margin(cfg.getint('PICTURE', 'margin_thick')) backgrounds = cfg.gettuple('PICTURE', 'backgrounds', ('color', 'path'), 2) factory.set_background(backgrounds[opt_index]) overlays = cfg.gettuple('PICTURE', 'overlays', 'path', 2) if overlays[opt_index]: factory.set_overlay(overlays[opt_index]) texts = [ cfg.get('PICTURE', 'footer_text1').strip('"'), cfg.get('PICTURE', 'footer_text2').strip('"') ] colors = cfg.gettuple('PICTURE', 'text_colors', 'color', len(texts)) text_fonts = cfg.gettuple('PICTURE', 'text_fonts', str, len(texts)) alignments = cfg.gettuple('PICTURE', 'text_alignments', str, len(texts)) if any(elem != '' for elem in texts): for params in zip(texts, text_fonts, colors, alignments): factory.add_text(*params) if cfg.getboolean('PICTURE', 'captures_cropping'): factory.set_cropping() if cfg.getboolean('GENERAL', 'debug'): factory.set_outlines() @pibooth.hookimpl def pibooth_cleanup(self): self.factory_pool.quit() @pibooth.hookimpl def state_failsafe_enter(self, app): self._reset_vars(app) @pibooth.hookimpl def state_wait_enter(self, cfg, app): animated = self.factory_pool.get() if animated: app.previous_animated = itertools.cycle(animated) # Reset timeout in case of settings changed self.picture_destroy_timer.timeout = max( 0, cfg.getfloat('WINDOW', 'final_image_delay')) self.picture_destroy_timer.start() @pibooth.hookimpl def state_wait_do(self, cfg, app): if cfg.getfloat('WINDOW', 'final_image_delay' ) > 0 and self.picture_destroy_timer.is_timeout(): self._reset_vars(app) @pibooth.hookimpl def state_processing_enter(self, app): self.second_previous_picture = app.previous_picture self._reset_vars(app) @pibooth.hookimpl def state_processing_do(self, cfg, app): idx = app.capture_choices.index(app.capture_nbr) with timeit("Creating the final picture"): captures = app.camera.get_captures() factory = get_picture_factory(captures, cfg.get('PICTURE', 'orientation')) self._pm.hook.pibooth_setup_picture_factory(cfg=cfg, opt_index=idx, factory=factory) app.previous_picture = factory.build() savedir = cfg.getpath('GENERAL', 'directory') app.previous_picture_file = osp.join( savedir, osp.basename(app.dirname) + "_pibooth.jpg") factory.save(app.previous_picture_file) if cfg.getboolean('WINDOW', 'animate') and app.capture_nbr > 1: with timeit("Asyncronously generate pictures for animation"): for capture in captures: factory = get_picture_factory( (capture, ), cfg.get('PICTURE', 'orientation'), force_pil=True) self._pm.hook.pibooth_setup_picture_factory( cfg=cfg, opt_index=idx, factory=factory) self.factory_pool.add(factory) @pibooth.hookimpl def state_print_do(self, cfg, app, events): if app.find_capture_event(events): with timeit("Putting the capture in the forget folder"): file_dir, file_name = osp.split(app.previous_picture_file) forget_dir = osp.join(file_dir, "forget") if not os.path.exists(forget_dir): os.makedirs(forget_dir) os.rename(app.previous_picture_file, osp.join(forget_dir, file_name)) self._reset_vars(app) app.previous_picture = self.second_previous_picture # Deactivate the print function for the backuped picture # as we don't known how many times it has already been printed app.nbr_duplicates = cfg.getint('PRINTER', 'max_duplicates') + 1
class PicturePlugin(object): """Plugin to build the final picture. """ def __init__(self, plugin_manager): self._pm = plugin_manager self.factory_pool = PicturesFactoryPool() self.picture_destroy_timer = PoolingTimer(0) self.second_previous_picture = None def _reset_vars(self, app): """Destroy final picture (can not be used anymore). """ self.factory_pool.clear() app.previous_picture = None app.previous_animated = None app.previous_picture_file = None @pibooth.hookimpl(hookwrapper=True) def pibooth_setup_picture_factory(self, cfg, opt_index, factory): outcome = yield # all corresponding hookimpls are invoked here factory = outcome.get_result() or factory factory.set_margin(cfg.getint('PICTURE', 'margin_thick')) backgrounds = cfg.gettuple('PICTURE', 'backgrounds', ('color', 'path'), 2) factory.set_background(backgrounds[opt_index]) overlays = cfg.gettuple('PICTURE', 'overlays', 'path', 2) if overlays[opt_index]: factory.set_overlay(overlays[opt_index]) texts = [ cfg.get('PICTURE', 'footer_text1').strip('"'), cfg.get('PICTURE', 'footer_text2').strip('"') ] colors = cfg.gettuple('PICTURE', 'text_colors', 'color', len(texts)) text_fonts = cfg.gettuple('PICTURE', 'text_fonts', str, len(texts)) alignments = cfg.gettuple('PICTURE', 'text_alignments', str, len(texts)) if any(elem != '' for elem in texts): for params in zip(texts, text_fonts, colors, alignments): factory.add_text(*params) if cfg.getboolean('PICTURE', 'captures_cropping'): factory.set_cropping() if cfg.getboolean('GENERAL', 'debug'): factory.set_outlines() outcome.force_result(factory) @pibooth.hookimpl def pibooth_cleanup(self): self.factory_pool.quit() @pibooth.hookimpl def state_failsafe_enter(self, app): self._reset_vars(app) @pibooth.hookimpl def state_wait_enter(self, cfg, app): animated = self.factory_pool.get() if cfg.getfloat('WINDOW', 'wait_image_delay') == 0: # Do it here to avoid a transient display of the picture self._reset_vars(app) elif animated: app.previous_animated = itertools.cycle(animated) # Reset timeout in case of settings changed self.picture_destroy_timer.timeout = max( 0, cfg.getfloat('WINDOW', 'wait_image_delay')) self.picture_destroy_timer.start() @pibooth.hookimpl def state_wait_do(self, cfg, app): if cfg.getfloat('WINDOW', 'wait_image_delay') > 0 and self.picture_destroy_timer.is_timeout()\ and app.previous_picture_file: self._reset_vars(app) @pibooth.hookimpl def state_processing_enter(self, app): self.second_previous_picture = app.previous_picture self._reset_vars(app) @pibooth.hookimpl def state_processing_do(self, cfg, app): idx = app.capture_choices.index(app.capture_nbr) with timeit("Saving raw captures"): captures = app.camera.get_captures() for savedir in cfg.gettuple('GENERAL', 'directory', 'path'): rawdir = osp.join(savedir, "raw", app.capture_date) os.makedirs(rawdir) for capture in captures: count = captures.index(capture) capture.save( osp.join(rawdir, "pibooth{:03}.jpg".format(count))) with timeit("Creating the final picture"): default_factory = get_picture_factory( captures, cfg.get('PICTURE', 'orientation')) factory = self._pm.hook.pibooth_setup_picture_factory( cfg=cfg, opt_index=idx, factory=default_factory) app.previous_picture = factory.build() for savedir in cfg.gettuple('GENERAL', 'directory', 'path'): app.previous_picture_file = osp.join(savedir, app.picture_filename) factory.save(app.previous_picture_file) if cfg.getboolean('WINDOW', 'animate') and app.capture_nbr > 1: with timeit("Asyncronously generate pictures for animation"): for capture in captures: default_factory = get_picture_factory( (capture, ), cfg.get('PICTURE', 'orientation'), force_pil=True, dpi=200) factory = self._pm.hook.pibooth_setup_picture_factory( cfg=cfg, opt_index=idx, factory=default_factory) factory.set_margin(factory._margin // 3) # 1/3 since DPI is divided by 3 self.factory_pool.add(factory) @pibooth.hookimpl def state_processing_exit(self, app): app.count.taken += 1 # Do it here because 'print' state can be skipped @pibooth.hookimpl def state_print_do(self, cfg, app, events): if app.find_capture_event(events): with timeit("Moving the picture in the forget folder"): for savedir in cfg.gettuple('GENERAL', 'directory', 'path'): forgetdir = osp.join(savedir, "forget") if not osp.isdir(forgetdir): os.makedirs(forgetdir) os.rename(osp.join(savedir, app.picture_filename), osp.join(forgetdir, app.picture_filename)) self._reset_vars(app) app.count.forgotten += 1 app.previous_picture = self.second_previous_picture # Deactivate the print function for the backuped picture # as we don't known how many times it has already been printed app.count.remaining_duplicates = 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()
class ViewPlugin(object): """Plugin to manage the pibooth window dans transitions. """ def __init__(self, plugin_manager): self._pm = plugin_manager self.count = 0 self.forgotten = False # Seconds to display the failed message self.failed_view_timer = PoolingTimer(2) # Seconds between each animated frame self.animated_frame_timer = PoolingTimer(0) # Seconds before going back to the start self.choose_timer = PoolingTimer(30) # Seconds to display the selected layout self.layout_timer = PoolingTimer(4) # Seconds to display the selected layout self.print_view_timer = PoolingTimer(0) # Seconds to display the selected layout self.finish_timer = PoolingTimer(1) @pibooth.hookimpl def state_failsafe_enter(self, win): win.show_oops() self.failed_view_timer.start() LOGGER.error(get_crash_message()) @pibooth.hookimpl def state_failsafe_validate(self): if self.failed_view_timer.is_timeout(): return 'wait' @pibooth.hookimpl def state_wait_enter(self, cfg, app, win): self.forgotten = False if app.previous_animated: previous_picture = next(app.previous_animated) # Reset timeout in case of settings changed self.animated_frame_timer.timeout = cfg.getfloat( 'WINDOW', 'animate_delay') self.animated_frame_timer.start() else: previous_picture = app.previous_picture win.show_intro( previous_picture, app.printer.is_available() and app.count.remaining_duplicates > 0) if app.printer.is_installed(): win.set_print_number(len(app.printer.get_all_tasks()), not app.printer.is_available()) @pibooth.hookimpl def state_wait_do(self, app, win, events): if app.previous_animated and self.animated_frame_timer.is_timeout(): previous_picture = next(app.previous_animated) win.show_intro( previous_picture, app.printer.is_available() and app.count.remaining_duplicates > 0) self.animated_frame_timer.start() else: previous_picture = app.previous_picture event = app.find_print_status_event(events) if event and app.printer.is_installed(): win.set_print_number(len(event.tasks), not app.printer.is_available()) if app.find_print_event(events) or (win.get_image() and not previous_picture): win.show_intro( previous_picture, app.printer.is_available() and app.count.remaining_duplicates > 0) @pibooth.hookimpl def state_wait_validate(self, app, events): if app.find_capture_event(events): if len(app.capture_choices) > 1: return 'choose' return 'preview' # No choice @pibooth.hookimpl def state_wait_exit(self, win): self.count = 0 win.show_image(None) # Clear currently displayed image @pibooth.hookimpl def state_choose_enter(self, app, win): with timeit("Show picture choice (nothing selected)"): win.set_print_number(0, False) # Hide printer status win.show_choice(app.capture_choices) self.choose_timer.start() @pibooth.hookimpl def state_choose_validate(self, app): if app.capture_nbr: return 'chosen' elif self.choose_timer.is_timeout(): return 'wait' @pibooth.hookimpl def state_chosen_enter(self, app, win): with timeit("Show picture choice ({} captures selected)".format( app.capture_nbr)): win.show_choice(app.capture_choices, selected=app.capture_nbr) self.layout_timer.start() @pibooth.hookimpl def state_chosen_validate(self): if self.layout_timer.is_timeout(): return 'preview' @pibooth.hookimpl def state_preview_enter(self, app, win): self.count += 1 win.set_capture_number(self.count, app.capture_nbr) @pibooth.hookimpl def state_preview_validate(self): return 'capture' @pibooth.hookimpl def state_capture_do(self, app, win): win.set_capture_number(self.count, app.capture_nbr) @pibooth.hookimpl def state_capture_validate(self, app): if self.count >= app.capture_nbr: return 'processing' return 'preview' @pibooth.hookimpl def state_processing_enter(self, win): win.show_work_in_progress() @pibooth.hookimpl def state_processing_validate(self, cfg, app): if app.printer.is_available() and cfg.getfloat('PRINTER', 'printer_delay') > 0\ and app.count.remaining_duplicates > 0: return 'print' return 'finish' # Can not print @pibooth.hookimpl def state_print_enter(self, cfg, app, win): with timeit("Display the final picture"): win.set_print_number(len(app.printer.get_all_tasks()), not app.printer.is_available()) win.show_print(app.previous_picture) # Reset timeout in case of settings changed self.print_view_timer.timeout = cfg.getfloat('PRINTER', 'printer_delay') self.print_view_timer.start() @pibooth.hookimpl def state_print_validate(self, app, win, events): printed = app.find_print_event(events) self.forgotten = app.find_capture_event(events) if self.print_view_timer.is_timeout() or printed or self.forgotten: if printed: win.set_print_number(len(app.printer.get_all_tasks()), not app.printer.is_available()) return 'finish' @pibooth.hookimpl def state_finish_enter(self, cfg, app, win): if cfg.getfloat('WINDOW', 'finish_image_delay') > 0 and not self.forgotten: win.show_finished(app.previous_picture) timeout = cfg.getfloat('WINDOW', 'finish_image_delay') else: win.show_finished() timeout = 1 # Reset timeout in case of settings changed self.finish_timer.timeout = timeout self.finish_timer.start() @pibooth.hookimpl def state_finish_validate(self): if self.finish_timer.is_timeout(): return 'wait'
class StateWait(State): def __init__(self): State.__init__(self, 'wait') self.timer = PoolingTimer( self.app.config.getfloat('WINDOW', 'animate_delay')) if self.app.config.getfloat('WINDOW', 'final_image_delay') < 0: self.final_display_timer = None else: self.final_display_timer = PoolingTimer( self.app.config.getfloat('WINDOW', 'final_image_delay')) def entry_actions(self): animated = self.app.makers_pool.get() if self.app.config.getfloat('WINDOW', 'final_image_delay') < 0: self.final_display_timer = None else: self.final_display_timer = PoolingTimer( self.app.config.getfloat('WINDOW', 'final_image_delay')) if self.final_display_timer and self.final_display_timer.is_timeout(): previous_picture = None elif self.app.config.getboolean('WINDOW', 'animate') and animated: self.app.previous_animated = itertools.cycle(animated) previous_picture = next(self.app.previous_animated) self.timer.timeout = self.app.config.getfloat( 'WINDOW', 'animate_delay') self.timer.start() else: previous_picture = self.app.previous_picture self.app.window.show_intro( previous_picture, self.app.printer.is_installed() and self.app.nbr_duplicates < self.app.config.getint( 'PRINTER', 'max_duplicates') and not self.app.printer_unavailable) self.app.window.set_print_number(len(self.app.printer.get_all_tasks()), self.app.printer_unavailable) self.app.led_capture.blink() if self.app.previous_picture_file and self.app.printer.is_installed( ) and not self.app.printer_unavailable: self.app.led_print.blink() def do_actions(self, events): if self.app.config.getboolean( 'WINDOW', 'animate' ) and self.app.previous_animated and self.timer.is_timeout(): previous_picture = next(self.app.previous_animated) self.app.window.show_intro( previous_picture, self.app.printer.is_installed() and self.app.nbr_duplicates < self.app.config.getint( 'PRINTER', 'max_duplicates') and not self.app.printer_unavailable) self.timer.start() else: previous_picture = self.app.previous_picture if self.app.find_print_event(events) and self.app.previous_picture_file and self.app.printer.is_installed()\ and not (self.final_display_timer and self.final_display_timer.is_timeout()): if self.app.nbr_duplicates >= self.app.config.getint( 'PRINTER', 'max_duplicates'): LOGGER.warning( "Too many duplicates sent to the printer (%s max)", self.app.config.getint('PRINTER', 'max_duplicates')) return elif self.app.printer_unavailable: LOGGER.warning( "Maximum number of printed pages reached (%s/%s max)", self.app.printer.nbr_printed, self.app.config.getint('PRINTER', 'max_pages')) return with timeit("Send final picture to printer"): self.app.led_print.switch_on() self.app.printer.print_file( self.app.previous_picture_file, self.app.config.getint('PRINTER', 'pictures_per_page')) time.sleep(1) # Just to let the LED switched on self.app.nbr_duplicates += 1 if self.app.nbr_duplicates >= self.app.config.getint( 'PRINTER', 'max_duplicates') or self.app.printer_unavailable: self.app.window.show_intro(previous_picture, False) self.app.led_print.switch_off() else: self.app.led_print.blink() event = self.app.find_print_status_event(events) if event: self.app.window.set_print_number(len(event.tasks), self.app.printer_unavailable) if self.final_display_timer and self.final_display_timer.is_timeout(): self.app.window.show_intro(None, False) def exit_actions(self): self.app.led_capture.switch_off() self.app.led_print.switch_off() # Clear currently displayed image self.app.window.show_image(None) def validate_transition(self, events): if self.app.find_capture_event(events): if len(self.app.capture_choices) > 1: return 'choose' else: self.app.capture_nbr = self.app.capture_choices[0] return 'capture'