def _post_event(self, event, s, **kwargs): event_name_placeholder = TextTemplate( self.machine, event.replace("(", "{").replace(")", "}")) for key, param in s.items(): if isinstance(param, dict): s[key] = self._evaluate_event_param(param, kwargs) self.machine.events.post(event_name_placeholder.evaluate(kwargs), **s)
def _post_event(self, event, priority, params, **kwargs): if "(" in event: # TODO: move this to parsing time event_name_placeholder = TextTemplate( self.machine, event.replace("(", "{").replace(")", "}")) event = event_name_placeholder.evaluate(kwargs) for key, param in params.items(): if isinstance(param, dict): params = deepcopy(params) # TODO: move this to parsing time params[key] = self._evaluate_event_param(param, kwargs) self.machine.events.post(event, priority=priority, **params)
def __init__(self, machine, transition: TransitionBase, current_text: str, new_text: str, current_colors: Optional[List[RGBColor]] = None, new_colors: Optional[List[RGBColor]] = None) -> None: """Class initializer.""" self._transition = transition self._step = 0 self._current_placeholder = TextTemplate(machine, current_text) self._new_placeholder = TextTemplate(machine, new_text) self._current_colors = current_colors self._new_colors = new_colors
def _load_widget_by_name(self, config) -> dict: # Replace placeholders if "(" in config['widget'] and ")" in config['widget']: config = dict(config) config["widget"] = TextTemplate(self.machine, config['widget'].replace("(", "{").replace(")", "}")) return config
def _update_stack(self) -> None: """Sort stack and show top entry on display.""" # do nothing if stack is emtpy. set display empty if not self._text_stack: self.hw_display.set_text("", flashing=False) if self._current_placeholder: self.text = "" self._current_placeholder = None return # sort stack by priority self._text_stack.sort(key=attrgetter("priority"), reverse=True) # get top entry top_entry = self._text_stack[0] self._current_placeholder = TextTemplate(self.machine, top_entry.text) self._update_display()
def _stop_transition(self): """Stop the current transition.""" if self._transition_update_task: self._transition_update_task.cancel() self._transition_update_task = None if self._current_transition: self._current_transition = None if self._current_text_stack_entry: # update placeholder if len(self._current_text_stack_entry.text) > 0: self._current_placeholder = TextTemplate( self.machine, self._current_text_stack_entry.text) self._current_placeholder_changed() else: self._current_placeholder = None
def _load_widget_by_name(self, config) -> dict: # Replace placeholders if "(" in config['widget'] and ")" in config['widget']: config = dict(config) config["widget"] = TextTemplate( self.machine, config['widget'].replace("(", "{").replace(")", "}")) elif config['widget'] not in self.mc.widgets: raise ValueError('"{}" is not a valid widget name.'.format( config['widget'])) return config
class TransitionRunner: """Class to run/execute transitions using an iterator.""" __slots__ = [ "_transition", "_step", "_current_placeholder", "_new_placeholder", "_current_colors", "_new_colors" ] # pylint: disable=too-many-arguments def __init__(self, machine, transition: TransitionBase, current_text: str, new_text: str, current_colors: Optional[List[RGBColor]] = None, new_colors: Optional[List[RGBColor]] = None) -> None: """Class initializer.""" self._transition = transition self._step = 0 self._current_placeholder = TextTemplate(machine, current_text) self._new_placeholder = TextTemplate(machine, new_text) self._current_colors = current_colors self._new_colors = new_colors def __iter__(self): """Return the iterator.""" return self def __next__(self): """Evaluate and return the next transition step.""" if self._step >= self._transition.get_step_count(): raise StopIteration transition_step = self._transition.get_transition_step( self._step, self._current_placeholder.evaluate({}), self._new_placeholder.evaluate({}), self._current_colors, self._new_colors) self._step += 1 return transition_step
class SegmentDisplay(SystemWideDevice): """A physical segment display in a pinball machine.""" config_section = 'segment_displays' collection = 'segment_displays' class_label = 'segment_display' def __init__(self, machine, name: str) -> None: """Initialise segment display device.""" super().__init__(machine, name) self.hw_display = None # type: SegmentDisplayPlatformInterface self.platform = None self._text_stack = [] # type: List[TextStack] self._current_placeholder = None # type: TextTemplate self.text = "" # type: str def _initialize(self): """Initialise display.""" # load platform self.platform = self.machine.get_platform_sections( 'segment_displays', self.config['platform']) # configure hardware self.hw_display = self.platform.configure_segment_display( self.config['number']) def add_text(self, text: str, priority: int = 0, key: str = None) -> None: """Add text to display stack.""" self._text_stack.append(TextStack(text, priority, key)) self._update_stack() def remove_text_by_key(self, key: str): """Remove entry from text stack.""" self._text_stack[:] = [x for x in self._text_stack if x.key != key] self._update_stack() def _update_stack(self) -> None: """Sort stack and show top entry on display.""" # do nothing if stack is emtpy. set display empty if not self._text_stack: self.hw_display.set_text("") if self._current_placeholder: self._current_placeholder.stop_monitor() self._current_placeholder = None return # sort stack by priority self._text_stack.sort(key=attrgetter("priority"), reverse=True) # get top entry top_entry = self._text_stack[0] if self._current_placeholder: self._current_placeholder.stop_monitor() self._current_placeholder = TextTemplate(self.machine, top_entry.text) self._current_placeholder.monitor_changes(self._update_display) self._update_display() def _update_display(self) -> None: """Update display to current text.""" if not self._current_placeholder: new_text = "" else: new_text = self._current_placeholder.evaluate() # set text to display if it changed if new_text != self.text: self.text = new_text self.hw_display.set_text(self.text)
class SegmentDisplay(SystemWideDevice): """A physical segment display in a pinball machine.""" config_section = 'segment_displays' collection = 'segment_displays' class_label = 'segment_display' def __init__(self, machine, name: str) -> None: """Initialise segment display device.""" super().__init__(machine, name) self.hw_display = None # type: SegmentDisplayPlatformInterface self.platform = None self._text_stack = [] # type: List[TextStack] self._current_placeholder = None # type: TextTemplate self.text = "" # type: str self.flashing = False # type: bool def _initialize(self): """Initialise display.""" # load platform self.platform = self.machine.get_platform_sections( 'segment_displays', self.config['platform']) # configure hardware self.hw_display = self.platform.configure_segment_display( self.config['number']) def add_text(self, text: str, priority: int = 0, key: str = None) -> None: """Add text to display stack. This will replace texts with the same key. """ # remove old text in case it has the same key self._text_stack[:] = [x for x in self._text_stack if x.key != key] # add new text self._text_stack.append(TextStack(text, priority, key)) self._update_stack() def set_flashing(self, flashing: bool): """Enable/Disable flashing.""" self.flashing = flashing # invalidate text to force an update self.text = None self._update_display() def remove_text_by_key(self, key: str): """Remove entry from text stack.""" self._text_stack[:] = [x for x in self._text_stack if x.key != key] self._update_stack() def _update_stack(self) -> None: """Sort stack and show top entry on display.""" # do nothing if stack is emtpy. set display empty if not self._text_stack: self.hw_display.set_text("", flashing=False) if self._current_placeholder: self.text = "" self._current_placeholder = None return # sort stack by priority self._text_stack.sort(key=attrgetter("priority"), reverse=True) # get top entry top_entry = self._text_stack[0] self._current_placeholder = TextTemplate(self.machine, top_entry.text) self._update_display() def _update_display(self, *args, **kwargs) -> None: """Update display to current text.""" del args del kwargs if not self._current_placeholder: new_text = "" else: new_text, future = self._current_placeholder.evaluate_and_subscribe( {}) future.add_done_callback(self._update_display) # set text to display if it changed if new_text != self.text: self.text = new_text self.hw_display.set_text(self.text, flashing=self.flashing)
def testTextTemplate(self): t = TextTemplate(self.machine, "Number: {test:<4d}") self.assertEqual("Number: 7 ", t.evaluate({"test": 7})) self.assertEqual("Number: 0 ", t.evaluate({"test": None}))
def _post_event(self, event, s): event_name_placeholder = TextTemplate( self.machine, event.replace("(", "{").replace(")", "}")) self.machine.events.post(event_name_placeholder.evaluate({}), **s)
class SegmentDisplay(SystemWideDevice): """A physical segment display in a pinball machine.""" __slots__ = [ "hw_display", "size", "virtual_connector", "_text_stack", "_current_placeholder", "_current_text_stack_entry", "_transition_update_task", "_current_transition", "_default_color", "_current_state" ] config_section = 'segment_displays' collection = 'segment_displays' class_label = 'segment_display' def __init__(self, machine, name: str) -> None: """Initialise segment display device.""" super().__init__(machine, name) self.hw_display = None # type: Optional[SegmentDisplayPlatformInterface] self.platform = None # type: Optional[SegmentDisplayPlatform] self.size = None # type: Optional[int] self.virtual_connector = None # type: Optional[VirtualSegmentDisplayConnector] self._text_stack = {} # type: Dict[str, TextStackEntry] self._current_placeholder = None # type: Optional[TextTemplate] self._current_text_stack_entry = None # type: Optional[TextStackEntry] self._transition_update_task = None # type: Optional[PeriodicTask] self._current_transition = None # type: Optional[TransitionRunner] self._default_color = None # type: Optional[RGBColor] self._current_state = None # type: Optional[SegmentDisplayState] async def _initialize(self): """Initialise display.""" await super()._initialize() # load platform self.platform = self.machine.get_platform_sections( 'segment_displays', self.config['platform']) self.platform.assert_has_feature("segment_displays") if not self.platform.features[ 'allow_empty_numbers'] and self.config['number'] is None: self.raise_config_error("Segment Display must have a number.", 1) self.size = self.config['size'] self._default_color = [ RGBColor(color) for color in self.config["default_color"][0:self.size] ] if len(self._default_color) < self.size: self._default_color += [RGBColor("white") ] * (self.size - len(self._default_color)) # configure hardware try: self.hw_display = await self.platform.configure_segment_display( self.config['number'], self.size, self.config['platform_settings']) except AssertionError as ex: raise AssertionError( "Error in platform while configuring segment display {}. " "See error above.".format(self.name)) from ex text = SegmentDisplayText.from_str("", self.size, self.config['integrated_dots'], self.config['integrated_commas'], self._default_color) self._update_display( SegmentDisplayState(text, FlashingType.NO_FLASH, '')) def add_virtual_connector(self, virtual_connector): """Add a virtual connector instance to connect this segment display to the MPF-MC for virtual displays.""" self.virtual_connector = virtual_connector def remove_virtual_connector(self): """Remove the virtual connector instance from this segment display.""" self.virtual_connector = None def validate_and_parse_config(self, config: dict, is_mode_config: bool, debug_prefix: str = None) -> dict: """Return the parsed and validated config. Args: ---- config: Config of device is_mode_config: Whether this device is loaded in a mode or system-wide debug_prefix: Prefix to use when logging. Returns: Validated config """ config = super().validate_and_parse_config(config, is_mode_config, debug_prefix) platform = self.machine.get_platform_sections( 'segment_displays', config.get("platform", None)) platform.assert_has_feature("segment_displays") config[ 'platform_settings'] = platform.validate_segment_display_section( self, config.get('platform_settings', None)) return config # pylint: disable-msg=too-many-arguments def add_text_entry(self, text, color, flashing, flash_mask, transition, transition_out, priority, key): """Add text to display stack. This will replace texts with the same key. """ # remove old text in case it has the same key self._text_stack[key] = TextStackEntry(text, color, flashing, flash_mask, transition, transition_out, priority, key) self._update_stack() def add_text(self, text: str, priority: int = 0, key: str = None) -> None: """Add text to display stack. This will replace texts with the same key. """ self.add_text_entry(text, None, None, None, None, None, priority, key) def remove_text_by_key(self, key: Optional[str]): """Remove entry from text stack.""" if key in self._text_stack: del self._text_stack[key] self._update_stack() # pylint: disable=too-many-arguments def _start_transition(self, transition: TransitionBase, current_text: str, new_text: str, current_colors: List[RGBColor], new_colors: List[RGBColor], update_hz: float, flashing, flash_mask): """Start the specified transition.""" current_colors = self._expand_colors(current_colors, len(current_text)) new_colors = self._expand_colors(new_colors, len(new_text)) if self._current_transition: self._stop_transition() self._current_transition = TransitionRunner(self.machine, transition, current_text, new_text, current_colors, new_colors) transition_text = next(self._current_transition) self._update_display( SegmentDisplayState(transition_text, flashing, flash_mask)) self._transition_update_task = self.machine.clock.schedule_interval( self._update_transition, 1 / update_hz) def _update_transition(self): """Update the current transition (callback function from transition interval clock).""" try: transition_text = next(self._current_transition) self._update_display( SegmentDisplayState(transition_text, self._current_state.flashing, self._current_state.flash_mask)) except StopIteration: self._stop_transition() def _stop_transition(self): """Stop the current transition.""" if self._transition_update_task: self._transition_update_task.cancel() self._transition_update_task = None if self._current_transition: self._current_transition = None if self._current_text_stack_entry: # update placeholder if len(self._current_text_stack_entry.text) > 0: self._current_placeholder = TextTemplate( self.machine, self._current_text_stack_entry.text) self._current_placeholder_changed() else: self._current_placeholder = None def _expand_colors(self, colors, length): """Expand color to a certain length.""" if not colors: colors = self._default_color if len(colors) > length: colors = colors[0:length] elif len(colors) < length: colors = colors + [colors[len(colors) - 1] ] * (length - len(colors)) return colors def _update_stack(self) -> None: """Sort stack and show top entry on display.""" # do nothing if stack is emtpy. set display empty assert self.hw_display is not None if not self._text_stack: top_text_stack_entry = TextStackEntry(" " * self.size, None, FlashingType.NO_FLASH, "", None, None, -999999, "") else: # sort text stack by priority self._text_stack = OrderedDict( sorted(self._text_stack.items(), key=lambda item: item[1].priority, reverse=True)) # get top entry (highest priority) top_text_stack_entry = next(iter(self._text_stack.values())) previous_text_stack_entry = self._current_text_stack_entry self._current_text_stack_entry = top_text_stack_entry # determine if the new key is different than the previous key (out transitions are only applied # when changing keys) transition_config = None if previous_text_stack_entry and top_text_stack_entry.key != previous_text_stack_entry.key and \ previous_text_stack_entry.transition_out: transition_config = previous_text_stack_entry.transition_out # determine if new text entry has a transition, if so, apply it (overrides any outgoing transition) if top_text_stack_entry.transition: transition_config = top_text_stack_entry.transition # start transition (if configured) if transition_config: transition = TransitionManager.get_transition( self.size, self.config['integrated_dots'], self.config['integrated_commas'], transition_config) if previous_text_stack_entry: previous_text = previous_text_stack_entry.text else: previous_text = " " * self.size if top_text_stack_entry.flashing is not None: flashing = top_text_stack_entry.flashing flash_mask = top_text_stack_entry.flash_mask else: flashing = self._current_state.flashing flash_mask = self._current_state.flash_mask self._start_transition(transition, previous_text, top_text_stack_entry.text, self._current_state.text.get_colors(), top_text_stack_entry.colors, self.config['default_transition_update_hz'], flashing, flash_mask) else: # no transition - subscribe to text template changes and update display self._current_placeholder = TextTemplate(self.machine, top_text_stack_entry.text) new_text, future = self._current_placeholder.evaluate_and_subscribe( {}) future.add_done_callback(self._current_placeholder_changed) # set any flashing state specified in the entry if top_text_stack_entry.flashing is not None: flashing = top_text_stack_entry.flashing flash_mask = top_text_stack_entry.flash_mask else: flashing = self._current_state.flashing flash_mask = self._current_state.flash_mask # update colors if specified if top_text_stack_entry.colors: colors = top_text_stack_entry.colors else: colors = self._current_state.text.get_colors() # update the display text = SegmentDisplayText.from_str( new_text, self.size, self.config['integrated_dots'], self.config['integrated_commas'], colors) self._update_display( SegmentDisplayState(text, flashing, flash_mask)) def _current_placeholder_changed(self, *args, **kwargs) -> None: """Update display when a placeholder changes (callback function).""" del args del kwargs new_text, future = self._current_placeholder.evaluate_and_subscribe({}) future.add_done_callback(self._current_placeholder_changed) text = SegmentDisplayText.from_str( new_text, self.size, self.config['integrated_dots'], self.config['integrated_commas'], self._current_state.text.get_colors()) self._update_display( SegmentDisplayState(text, self._current_state.flashing, self._current_state.flash_mask)) def set_flashing(self, flashing: FlashingType, flash_mask: str = ""): """Enable/Disable flashing.""" self._update_display( SegmentDisplayState(self._current_state.text, flashing, flash_mask)) def set_color(self, colors: List[RGBColor]): """Set display colors.""" assert isinstance(colors, list) assert self.hw_display is not None text = SegmentDisplayText.from_str( self._current_state.text.convert_to_str(), self.size, self.config['integrated_dots'], self.config['integrated_commas'], colors) self._update_display( SegmentDisplayState(text, self._current_state.flashing, self._current_state.flash_mask)) @property def text(self): """Return current text.""" return self._current_state.text.convert_to_str() @property def colors(self): """Return current colors.""" return self._current_state.text.get_colors() @property def flashing(self): """Return current flashing state.""" return self._current_state.flashing @property def flash_mask(self): """Return current flash mask.""" return self._current_state.flash_mask def _update_display(self, new_state: SegmentDisplayState) -> None: """Update display to current text.""" assert self.hw_display is not None if self._current_state and new_state == self._current_state: return self._current_state = new_state text = new_state.text flashing = new_state.flashing flash_mask = new_state.flash_mask assert len(text) == self.size # set text to display self.hw_display.set_text(text, flashing=flashing, flash_mask=flash_mask) if self.virtual_connector: self.virtual_connector.set_text(self.name, text, flashing=flashing, flash_mask=flash_mask)
def _update_stack(self) -> None: """Sort stack and show top entry on display.""" # do nothing if stack is emtpy. set display empty assert self.hw_display is not None if not self._text_stack: top_text_stack_entry = TextStackEntry(" " * self.size, None, FlashingType.NO_FLASH, "", None, None, -999999, "") else: # sort text stack by priority self._text_stack = OrderedDict( sorted(self._text_stack.items(), key=lambda item: item[1].priority, reverse=True)) # get top entry (highest priority) top_text_stack_entry = next(iter(self._text_stack.values())) previous_text_stack_entry = self._current_text_stack_entry self._current_text_stack_entry = top_text_stack_entry # determine if the new key is different than the previous key (out transitions are only applied # when changing keys) transition_config = None if previous_text_stack_entry and top_text_stack_entry.key != previous_text_stack_entry.key and \ previous_text_stack_entry.transition_out: transition_config = previous_text_stack_entry.transition_out # determine if new text entry has a transition, if so, apply it (overrides any outgoing transition) if top_text_stack_entry.transition: transition_config = top_text_stack_entry.transition # start transition (if configured) if transition_config: transition = TransitionManager.get_transition( self.size, self.config['integrated_dots'], self.config['integrated_commas'], transition_config) if previous_text_stack_entry: previous_text = previous_text_stack_entry.text else: previous_text = " " * self.size if top_text_stack_entry.flashing is not None: flashing = top_text_stack_entry.flashing flash_mask = top_text_stack_entry.flash_mask else: flashing = self._current_state.flashing flash_mask = self._current_state.flash_mask self._start_transition(transition, previous_text, top_text_stack_entry.text, self._current_state.text.get_colors(), top_text_stack_entry.colors, self.config['default_transition_update_hz'], flashing, flash_mask) else: # no transition - subscribe to text template changes and update display self._current_placeholder = TextTemplate(self.machine, top_text_stack_entry.text) new_text, future = self._current_placeholder.evaluate_and_subscribe( {}) future.add_done_callback(self._current_placeholder_changed) # set any flashing state specified in the entry if top_text_stack_entry.flashing is not None: flashing = top_text_stack_entry.flashing flash_mask = top_text_stack_entry.flash_mask else: flashing = self._current_state.flashing flash_mask = self._current_state.flash_mask # update colors if specified if top_text_stack_entry.colors: colors = top_text_stack_entry.colors else: colors = self._current_state.text.get_colors() # update the display text = SegmentDisplayText.from_str( new_text, self.size, self.config['integrated_dots'], self.config['integrated_commas'], colors) self._update_display( SegmentDisplayState(text, flashing, flash_mask))
class SegmentDisplay(SystemWideDevice): """A physical segment display in a pinball machine.""" config_section = 'segment_displays' collection = 'segment_displays' class_label = 'segment_display' def __init__(self, machine, name: str) -> None: """Initialise segment display device.""" super().__init__(machine, name) self.hw_display = None # type: Optional[SegmentDisplayPlatformInterface] self.platform = None # type: Optional[SegmentDisplayPlatform] self._text_stack = [] # type: List[TextStack] self._current_placeholder = None # type: Optional[TextTemplate] self.text = "" # type: Optional[str] self.flashing = FlashingType.NO_FLASH # type: FlashingType async def _initialize(self): await super()._initialize() """Initialise display.""" # load platform self.platform = self.machine.get_platform_sections( 'segment_displays', self.config['platform']) self.platform.assert_has_feature("segment_displays") if not self.platform.features[ 'allow_empty_numbers'] and self.config['number'] is None: self.raise_config_error("Segment Display must have a number.", 1) # configure hardware try: self.hw_display = await self.platform.configure_segment_display( self.config['number'], self.config['platform_settings']) except AssertionError as e: raise AssertionError( "Error in platform while configuring segment display {}. " "See error above.".format(self.name)) from e def add_text(self, text: str, priority: int = 0, key: str = None) -> None: """Add text to display stack. This will replace texts with the same key. """ # remove old text in case it has the same key self._text_stack[:] = [x for x in self._text_stack if x.key != key] # add new text self._text_stack.append(TextStack(text, priority, key)) self._update_stack() def set_flashing(self, flashing: FlashingType): """Enable/Disable flashing.""" self.flashing = flashing # invalidate text to force an update self.text = None self._update_display() def remove_text_by_key(self, key: str): """Remove entry from text stack.""" self._text_stack[:] = [x for x in self._text_stack if x.key != key] self._update_stack() def _update_stack(self) -> None: """Sort stack and show top entry on display.""" # do nothing if stack is emtpy. set display empty assert self.hw_display is not None if not self._text_stack: self.hw_display.set_text("", flashing=FlashingType.NO_FLASH) if self._current_placeholder: self.text = "" self._current_placeholder = None return # sort stack by priority self._text_stack.sort(key=attrgetter("priority"), reverse=True) # get top entry top_entry = self._text_stack[0] self._current_placeholder = TextTemplate(self.machine, top_entry.text) self._update_display() def _update_display(self, *args, **kwargs) -> None: """Update display to current text.""" del args del kwargs assert self.hw_display is not None if not self._current_placeholder: new_text = "" else: new_text, future = self._current_placeholder.evaluate_and_subscribe( {}) future.add_done_callback(self._update_display) # set text to display if it changed if new_text != self.text: self.text = new_text self.hw_display.set_text(self.text, flashing=self.flashing)