def __init__(self, machine: MachineController) -> None: """Initialise switch controller.""" super().__init__(machine) self.registered_switches = CaseInsensitiveDict() # type: Dict[str, List[RegisteredSwitch]] # Dictionary of switches and states that have been registered for # callbacks. self._timed_switch_handler_delay = None # type: Any self.active_timed_switches = defaultdict(list) # type: Dict[float, List[TimedSwitchHandler]] # Dictionary of switches that are currently in a state counting ms # waiting to notify their handlers. In other words, this is the dict # that tracks current switches for things like "do foo() if switch bar # is active for 100ms." self.switches = CaseInsensitiveDict() # type: Dict[str, SwitchState] # Dictionary which holds the master list of switches as well as their # current states. State here does factor in whether a switch is NO or # NC so 1 = active and 0 = inactive. # register for events self.machine.events.add_async_handler('init_phase_2', self._initialize_switches, 1000) # priority 1000 so this fires first self.machine.events.add_handler('machine_reset_phase_3', self.log_active_switches) self.monitors = list() # type: List[Callable[[MonitoredSwitchChange], None]]
def configure_machine_var(self, name: str, persist: bool, expire_secs: int = None) -> None: """Create a new machine variable. Args: name: String name of the variable. persist: Boolean as to whether this variable should be saved to disk so it's available the next time MPF boots. expire_secs: Optional number of seconds you'd like this variable to persist on disk for. When MPF boots, if the expiration time of the variable is in the past, it will be loaded with a value of 0. For example, this lets you write the number of credits on the machine to disk to persist even during power off, but you could set it so that those only stay persisted for an hour. """ if name not in self.machine_vars: var = CaseInsensitiveDict() var['value'] = None var['persist'] = persist var['expire_secs'] = expire_secs self.machine_vars[name] = var else: self.machine_vars[name]['persist'] = persist self.machine_vars[name]['expire_sec'] = expire_secs
def create_machine_var(self, name, value=0, persist=False, expire_secs=None, silent=False): """Create a new machine variable. Args: name: String name of the variable. value: The value of the variable. This can be any Type. persist: Boolean as to whether this variable should be saved to disk so it's available the next time MPF boots. expire_secs: Optional number of seconds you'd like this variable to persist on disk for. When MPF boots, if the expiration time of the variable is in the past, it will be loaded with a value of 0. For example, this lets you write the number of credits on the machine to disk to persist even during power off, but you could set it so that those only stay persisted for an hour. """ var = CaseInsensitiveDict() var['value'] = value var['persist'] = persist var['expire_secs'] = expire_secs self.machine_vars[name] = var if not silent: self.set_machine_var(name, value, force_events=True)
def __init__(self, machine): """Initialize ExtraBallManager""" super().__init__(machine) self.extra_balls = CaseInsensitiveDict() self.config = self.machine.config_validator.validate_config( config_spec='global_extra_ball_settings', source=self.machine.config['global_extra_ball_settings']) self.enabled = self.config['enabled'] self.events_only = self.config['events_only'] self.machine.events.add_handler('player_add_success', self._player_added) self.machine.events.add_handler('player_turn_start', self._player_turn_start) self.machine.events.add_handler('player_turn_stop', self._player_turn_stop) self.machine.events.add_handler('award_extra_ball', self.award) '''event: award_extra_ball desc: This is an event you can post which will immediately award the player an extra ball (assuming they're within the limits of max extra balls, etc.). This event will in turn post the extra_ball_awarded event if the extra ball is able to be awarded. Note that if you want to just light the extra ball, but not award it right away, then use the :doc:`award_lit_extra_ball` event instead. Also note that if an extra ball is lit, this event will NOT unlight or decrement the lit extra ball count. If you want to do that, use the :doc:`award_lit_extra_ball` instead. ''' self.machine.events.add_handler('award_lit_extra_ball', self.award_lit) '''event: award_lit_extra_ball desc: This event will award an extra ball if extra ball is lit. If the player has no lit extra balls, then this event will have no effect. This is a good event to use in your extra ball mode or shot to post to collect the lit extra ball. It will in turn post the :doc:`extra_ball_awarded` event (assuming the player has not exceeded any configured limits for max extra balls). If you just want to award an extra ball regardless of whether the player has one lit, use the :doc:`award_extra_ball` event instead. ''' self.machine.events.add_handler('light_extra_ball', self.light) '''event: light_extra_ball
def _write_machine_var_to_disk(self, name: str) -> None: """Write value to disk.""" if self.machine_vars[name]['persist'] and self.config['mpf']['save_machine_vars_to_disk']: disk_var = CaseInsensitiveDict() disk_var['value'] = self.machine_vars[name]['value'] if self.machine_vars[name]['expire_secs']: disk_var['expire'] = self.clock.get_time() + self.machine_vars[name]['expire_secs'] self.machine_var_data_manager.save_key(name, disk_var)
def _get_merged_settings(self, section_name): """Return a dict of a config section from the machine-wide config with the mode-specific config merged in.""" if section_name in self.machine.config: return_dict = copy.deepcopy(self.machine.config[section_name]) else: return_dict = CaseInsensitiveDict() if section_name in self.config: return_dict = Util.dict_merge(return_dict, self.config[section_name], combine_lists=False) return return_dict
def register_asset_class(self, asset_class: str, attribute: str, config_section: str, disk_asset_section: str, path_string: str, extensions: Iterable[str], priority: int, pool_config_section: str) -> None: """Register a a type of assets to be controlled by the AssetManager. Args: asset_class: Reference to the class you want to register, based on mc.core.assets.Asset. e.g. mc.assets.images.ImageClass attribute: String of the name of the attribute dict that will be added to the main MpfMc instance. e.g. 'images' means that the dict of image names to image asset class instances will be at self.machine.images. config_section: String name of this assets section in the config files. e.g. 'images' disk_asset_section: String name of the section which holds settings for assets that are loaded from disk. path_string: String name of the setting from mpf-mc:paths: which controls the name of the folder that will hold this type of assets in the machine folder. e.g. 'images extensions: Tuple of strings, with no dots, of the types of file extensions that are valid for this type of asset. e.g. ('jpg', 'gif', 'png') priority: Integer of the relative priority of this asset class as compared to other asset classes. This affects the order that asset objects are created and loaded (when there's a tie) because some asset classes depend on others to exist first. e.g. 'slide_shows' assets need 'images', 'videos', and 'sounds' to exist. Higher number is first. pool_config_section: String which specifies the config file section for associated asset groups. """ if not hasattr(self.machine, attribute): # some assets of different classes use the same mc attribute, like # images and animated_images setattr(self.machine, attribute, CaseInsensitiveDict()) ac = AssetClass(attribute=attribute, cls=asset_class, path_string=path_string, config_section=config_section, disk_asset_section=disk_asset_section, extensions=extensions, priority=priority, pool_config_section=pool_config_section, defaults=self._get_asset_class_defaults( disk_asset_section, self.machine.machine_config)) self._asset_classes.append(ac) self._asset_classes.sort(key=lambda x: x.priority, reverse=True)
def __init__(self, machine, index): """Initialise player.""" # use self.__dict__ below since __setattr__ would make these player vars self.__dict__['log'] = logging.getLogger("Player") self.__dict__['machine'] = machine self.__dict__['vars'] = CaseInsensitiveDict() number = index + 1 self.log.debug("Creating new player: Player %s. (player index '%s')", number, index) # Set these after the player_add_success event so any player monitors # get notification of the new player before they start seeing variable # changes for it. self.vars['index'] = index '''player_var: index desc: The index of this player, starting with 0. For example, Player 1 has an index of 0, Player 2 has an index of 1, etc. If you want to get the player number, use the "number" player variable instead. ''' self.vars['number'] = number '''player_var: number desc: The number of the player, beginning with 1. (e.g. Player 1 has a number of "1", Player 2 is "2", etc. ''' self.machine.events.post('player_add_success', player=self, num=number, callback=self._player_add_done) '''event: player_add_success desc: A new player was just added to this game args: player: A reference to the instance of the Player() object. num: The number of the player that was just added. (e.g. Player 1 will have *num=1*, Player 4 will have *num=4*, etc.) ''' self._load_initial_player_vars()
def __init__(self, machine): """Initialise switch controller.""" super().__init__(machine) self.registered_switches = CaseInsensitiveDict() # Dictionary of switches and states that have been registered for # callbacks. self._timed_switch_handler_delay = None self.active_timed_switches = defaultdict(list) # Dictionary of switches that are currently in a state counting ms # waiting to notify their handlers. In other words, this is the dict # that tracks current switches for things like "do foo() if switch bar # is active for 100ms." self.switches = CaseInsensitiveDict() # Dictionary which holds the master list of switches as well as their # current states. State here does factor in whether a switch is NO or # NC so 1 = active and 0 = inactive. self.switch_event_active = ( self.machine.config['mpf']['switch_event_active']) self.switch_event_inactive = ( self.machine.config['mpf']['switch_event_inactive']) self.switch_tag_event = ( self.machine.config['mpf']['switch_tag_event']) # register for events self.machine.events.add_handler('init_phase_2', self._initialize_switches, 1000) # priority 1000 so this fires first self.machine.events.add_handler('machine_reset_phase_3', self.log_active_switches) self.monitors = list()
def load_machine_config(config_file_list, machine_path, config_path='config', existing_config=None): machine_config = dict() for num, config_file in enumerate(config_file_list): if not existing_config: machine_config = CaseInsensitiveDict() else: machine_config = existing_config if not (config_file.startswith('/') or config_file.startswith('\\')): config_file = os.path.join(machine_path, config_path, config_file) machine_config = Util.dict_merge(machine_config, ConfigProcessor.load_config_file(config_file, 'machine', ignore_unknown_sections=True)) return machine_config
def __init__(self, machine, index): """Initialise player.""" # use self.__dict__ below since __setattr__ would make these player vars self.__dict__['log'] = logging.getLogger("Player") self.__dict__['machine'] = machine self.__dict__['vars'] = CaseInsensitiveDict() self.__dict__['_events_enabled'] = False number = index + 1 self.log.debug("Creating new player: Player %s. (player index '%s')", number, index) # Set these after the player_added event so any player monitors # get notification of the new player before they start seeing variable # changes for it. self.vars['index'] = index '''player_var: index desc: The index of this player, starting with 0. For example, Player 1 has an index of 0, Player 2 has an index of 1, etc. If you want to get the player number, use the "number" player variable instead. ''' self.vars['number'] = number '''player_var: number desc: The number of the player, beginning with 1. (e.g. Player 1 has a number of "1", Player 2 is "2", etc. ''' self._load_initial_player_vars() # Set the initial player score to 0 self.__setattr__("score", 0) '''player_var: score
def __init__(self, options, config: MpfMcConfig, thread_stopper=None): self.log = logging.getLogger('mpfmc') self.log.info("Mission Pinball Framework Media Controller v%s", __version__) self.log.info("Mission Pinball Framework Game Engine v%s", __mpfversion__) if (__version__.split('.')[0] != __mpfversion__.split('.')[0] or __version__.split('.')[1] != __mpfversion__.split('.')[1]): self.log.error( "MPF MC and MPF Game engines must be same " "major.minor versions. You have MPF v%s and MPF-MC" " v%s", __mpfversion__, __version__) raise ValueError( "MPF MC and MPF Game engines must be same " "major.minor versions. You have MPF v{} and MPF-MC" " v{}".format(__mpfversion__, __version__)) super().__init__() self.options = options self.machine_path = config.get_machine_path() self.log.info("Machine path: %s", self.machine_path) # load machine into path to load modules if self.machine_path not in sys.path: sys.path.append(self.machine_path) self.mc_config = config self.config_validator = ConfigValidator(self, config.get_config_spec()) self.machine_config = self.mc_config.get_machine_config() self.config = self.machine_config self.clock = Clock # pylint: disable-msg=protected-access self.log.info("Starting clock at %sHz", Clock._max_fps) self._boot_holds = set() self.is_init_done = threading.Event() self.mpf_path = os.path.dirname(mpf.__file__) self.modes = CaseInsensitiveDict() self.player_list = list() self.player = None self.num_players = 0 self.bcp_client_connected = False self.placeholder_manager = McPlaceholderManager(self) self.settings = McSettingsController(self) self.animation_configs = dict() self.active_slides = dict() self.custom_code = list() self.register_boot_hold('init') self.displays = DeviceCollection(self, "displays", "displays") self.machine_vars = CaseInsensitiveDict() self.machine_var_monitor = False self.monitors = dict() self.targets = dict() """Dict which contains all the active slide frames in the machine that a slide can target. Will always contain an entry called 'default' which will be used if a slide doesn't specify targeting. """ self.keyboard = None self.dmds = [] self.rgb_dmds = [] self.crash_queue = queue.Queue() self.ticks = 0 self.start_time = 0 self.debug_refs = [] MYPY = False # NOQA if MYPY: # pragma: no cover self.videos = None # type: Dict[str, VideoAsset] if thread_stopper: self.thread_stopper = thread_stopper else: self.thread_stopper = threading.Event() # Core components self.events = EventManager(self) self.mode_controller = ModeController(self) create_config_collections( self, self.machine_config['mpf-mc']['config_collections']) self._preprocess_config(self.config) self.config_processor = ConfigProcessor(self) self.transition_manager = TransitionManager(self) self.effects_manager = EffectsManager(self) self._set_machine_path() self._load_font_paths() # Initialize the sound system (must be done prior to creating the AssetManager). # If the sound system is not available, do not load any other sound-related modules. if SoundSystem is None or self.options.get("no_sound"): self.sound_system = None else: self.sound_system = SoundSystem(self) if self.sound_system.audio_interface is None: self.sound_system = None self.asset_manager = ThreadedAssetManager(self) self.bcp_processor = BcpProcessor(self) # Asset classes ImageAsset.initialize(self) VideoAsset.initialize(self) BitmapFontAsset.initialize(self) self._initialise_sound_system() self.clock.schedule_interval(self._check_crash_queue, 1) self.events.add_handler("client_connected", self._create_dmds) self.events.add_handler("player_turn_start", self.player_start_turn) self.create_machine_var('mpfmc_ver', __version__) # force setting it here so we have it before MPF connects self.receive_machine_var_update('mpfmc_ver', __version__, 0, True)
def validate_config(self, config_spec, source, section_name=None, base_spec=None, add_missing_keys=True, prefix=None): """Validate a config dict against spec.""" # config_spec, str i.e. "device:shot" # source is dict # section_name is str used for logging failures if source is None: source = CaseInsensitiveDict() if not section_name: section_name = config_spec # str if prefix: validation_failure_info = (prefix + ":" + config_spec, section_name) else: validation_failure_info = (config_spec, section_name) this_spec = self._build_spec(config_spec, base_spec) if '__allow_others__' not in this_spec: self.check_for_invalid_sections(this_spec, source, validation_failure_info) processed_config = source if not isinstance(source, (list, dict)): self.validation_error("", validation_failure_info, "Source should be list or dict but is {}".format( source.__class__ )) for k in list(this_spec.keys()): if this_spec[k] == 'ignore' or k[0] == '_': continue elif k in source: # validate the entry that exists if isinstance(this_spec[k], dict): # This means we're looking for a list of dicts final_list = list() if k in source: for i in source[k]: # individual step final_list.append(self.validate_config( config_spec + ':' + k, source=i, section_name=k)) processed_config[k] = final_list else: processed_config[k] = self.validate_config_item( this_spec[k], item=source[k], validation_failure_info=(validation_failure_info, k)) elif add_missing_keys: # create the default entry if isinstance(this_spec[k], dict): processed_config[k] = list() else: processed_config[k] = self.validate_config_item( this_spec[k], validation_failure_info=( validation_failure_info, k)) return processed_config
def __init__(self, mpf_path: str, machine_path: str, options: dict) -> None: """Initialize machine controller.""" super().__init__() self.log = logging.getLogger("Machine") # type: Logger self.log.info("Mission Pinball Framework Core Engine v%s", __version__) self.log.info("Command line arguments: %s", options) self.options = options self.config_processor = ConfigProcessor() self.log.info("MPF path: %s", mpf_path) self.mpf_path = mpf_path self.log.info("Machine path: %s", machine_path) self.machine_path = machine_path self.verify_system_info() self._exception = None # type: Any self._boot_holds = set() # type: Set[str] self.is_init_done = None # type: asyncio.Event self._done = False self.monitors = dict() # type: Dict[str, Set[Callable]] self.plugins = list() # type: List[Any] self.scriptlets = list() # type: List[Scriptlet] self.modes = DeviceCollection(self, 'modes', None) # type: Dict[str, Mode] self.game = None # type: Game self.machine_vars = CaseInsensitiveDict() self.machine_var_monitor = False self.machine_var_data_manager = None # type: DataManager self.thread_stopper = threading.Event() self.config = None # type: Any # add some type hints MYPY = False # noqa if MYPY: # pragma: no cover # controllers self.events = None # type: EventManager self.switch_controller = None # type: SwitchController self.mode_controller = None # type: ModeController self.settings = None # type: SettingsController self.bcp = None # type: Bcp self.asset_manager = None # type: BaseAssetManager self.ball_controller = None # type: BallController self.show_controller = None # type: ShowController self.placeholder_manager = None # type: PlaceholderManager self.device_manager = None # type: DeviceManager self.auditor = None # type: Auditor self.tui = None # type: TextUi self.service = None # type: ServiceController # devices self.autofires = None # type: DeviceCollectionType[str, AutofireCoil] self.shows = None # type: DeviceCollectionType[str, Show] self.shots = None # type: DeviceCollectionType[str, Shot] self.shot_groups = None # type: DeviceCollectionType[str, ShotGroup] self.switches = None # type: DeviceCollectionType[str, Switch] self.coils = None # type: DeviceCollectionType[str, Driver] self.lights = None # type: DeviceCollectionType[str, Light] self.ball_devices = None # type: DeviceCollectionType[str, BallDevice] self.accelerometers = None # type: DeviceCollectionType[str, Accelerometer] self.playfield = None # type: Playfield self.playfields = None # type: DeviceCollectionType[str, Playfield] self.counters = None # type: DeviceCollectionType[str, Counter] self.sequences = None # type: DeviceCollectionType[str, Sequence] self.accruals = None # type: DeviceCollectionType[str, Accrual] self.drop_targets = None # type: DeviceCollectionType[str, DropTarget] self.servos = None # type: DeviceCollectionType[str, Servo] self.segment_displays = None # type: DeviceCollectionType[str, SegmentDisplay] self._set_machine_path() self.config_validator = ConfigValidator(self) self._load_config() self.machine_config = self.config # type: Any self.configure_logging( 'Machine', self.config['logging']['console']['machine_controller'], self.config['logging']['file']['machine_controller']) self.delayRegistry = DelayManagerRegistry(self) self.delay = DelayManager(self.delayRegistry) self.hardware_platforms = dict( ) # type: Dict[str, SmartVirtualHardwarePlatform] self.default_platform = None # type: SmartVirtualHardwarePlatform self.clock = self._load_clock() self.stop_future = asyncio.Future( loop=self.clock.loop) # type: asyncio.Future
class WidgetCollection(ConfigCollection): config_section = 'widgets' collection = 'widgets' class_label = 'WidgetConfig' type_map = CaseInsensitiveDict() def _initialize(self) -> None: for cls_name, module in self.mc.machine_config['mpf-mc'][ 'widgets'].items(): for widget_cls in import_module(module).widget_classes: self.type_map[cls_name] = widget_cls def process_config(self, config: Union[dict, list]) -> List["Widget"]: # config is localized to a specific widget section if isinstance(config, dict): config = [config] widget_list = list() for widget in config: widget_list.append(self.process_widget(widget)) return widget_list def process_widget(self, config: dict) -> dict: # config is localized widget settings try: widget_cls = WidgetCollection.type_map[config['type']] except (KeyError, TypeError): try: raise ValueError( '"{}" is not a valid MPF display widget type. Did you ' 'misspell it, or forget to enable it in the "mpf-mc: ' 'widgets" section of your machine config?'.format( config['type'])) except (KeyError, TypeError): raise ValueError("Invalid widget config: {}".format(config)) config['_default_settings'] = list() for default_setting_name in widget_cls.merge_settings: if default_setting_name in config: config['_default_settings'].append(default_setting_name) self.mc.config_validator.validate_config('widgets:{}'.format( config['type']).lower(), config, base_spec='widgets:common') if 'effects' in config and config['type'] == 'display': config['effects'] = self.mc.effects_manager.validate_effects( config['effects']) if 'animations' in config: config['animations'] = (self.process_animations( config['animations'])) else: config['animations'] = None if 'reset_animations_events' in config: for event_name in config['reset_animations_events']: if event_name not in magic_events: self.mc.events.add_handler( "client_connected", partial(self._register_trigger, event_name)) if config.get('z', 0) < 0: raise ValueError( "\nWidget with negative z value in config: {}.\n\nAs of MPF " "v0.30.3, negative z: " "values are no longer used to put widgets in 'parent' frames. " "Instead add a 'target:' setting to the 'widget_player:' entry" " and set that to the name of the display target (display or " "slide_frame) you want to add this widget to. Note that " "'target: default' is valid and will add the widget to the " "default display on top of any slides.\n".format(config)) return config def _register_trigger(self, event_name: str, **kwargs) -> None: del kwargs self.mc.bcp_processor.register_trigger(event=event_name) def process_animations(self, config: dict) -> dict: # config is localized to the slide's 'animations' section for event_name, event_settings in config.items(): # make sure the event_name is registered as a trigger event so MPF # will send those events as triggers via BCP. But we don't want # to register magic events since those aren't real MPF events. if event_name not in magic_events: self.mc.events.add_handler( "client_connected", partial(self._register_trigger, event_name)) # str means it's a list of named animations if isinstance(event_settings, str): event_settings = Util.string_to_list(event_settings) # dict means it's a single set of settings for one animation step elif isinstance(event_settings, dict): event_settings = [event_settings] # ultimately we're producing a list of dicts, so build that list # as we iterate new_list = list() for settings in event_settings: new_list.append(self.mc.animations.process_animation(settings)) config[event_name] = new_list return config
class Widget(ConfigCollection): config_section = 'widgets' collection = 'widgets' class_label = 'WidgetConfig' type_map = CaseInsensitiveDict(text=Text, image=ImageWidget, video=VideoWidget, slide_frame=SlideFrame, bezier=Bezier, ellipse=Ellipse, line=Line, point=Point, points=Point, quad=Quad, rectangle=Rectangle, triangle=Triangle, dmd=Dmd, color_dmd=ColorDmd, text_input=MpfTextInput) def process_config(self, config): # config is localized to a specific widget section if isinstance(config, dict): config = [config] widget_list = list() for widget in config: widget_list.append(self.process_widget(widget)) return widget_list def process_widget(self, config): # config is localized widget settings try: widget_cls = Widget.type_map[config['type']] except KeyError: try: raise ValueError( '"{}" is not a valid MPF display widget type'.format( config['type'])) except KeyError: raise ValueError("Invalid widget config: {}".format(config)) config['_default_settings'] = list() for default_setting_name in widget_cls.merge_settings: if default_setting_name in config: config['_default_settings'].append(default_setting_name) self.mc.config_validator.validate_config('widgets:{}'.format( config['type']).lower(), config, base_spec='widgets:common') if 'animations' in config: config['animations'] = (self.process_animations( config['animations'])) else: config['animations'] = None if config.get('z', 0) < 0: raise ValueError( "\nWidget with negative z value in config: {}.\n\nAs of MPF " "v0.30.3, negative z: " "values are no longer used to put widgets in 'parent' frames. " "Instead add a 'target:' setting to the 'widget_player:' entry" " and set that to the name of the display target (display or " "slide_frame) you want to add this widget to. Note that " "'target: default' is valid and will add the widget to the " "default display on top of any slides.\n".format(config)) return config def _register_trigger(self, event_name, **kwargs): del kwargs self.mc.bcp_processor.register_trigger(event=event_name) def process_animations(self, config): # config is localized to the slide's 'animations' section for event_name, event_settings in config.items(): # make sure the event_name is registered as a trigger event so MPF # will send those events as triggers via BCP. But we don't want # to register magic events since those aren't real MPF events. if event_name not in magic_events: self.mc.events.add_handler( "client_connected", partial(self._register_trigger, event_name)) # str means it's a list of named animations if isinstance(event_settings, str): event_settings = Util.string_to_list(event_settings) # dict means it's a single set of settings for one animation step elif isinstance(event_settings, dict): event_settings = [event_settings] # ultimately we're producing a list of dicts, so build that list # as we iterate new_list = list() for settings in event_settings: new_list.append(self.mc.animations.process_animation(settings)) config[event_name] = new_list return config
def __init__(self, options, config, machine_path, thread_stopper=None, **kwargs): self.log = logging.getLogger('mpfmc') self.log.info("Mission Pinball Framework Media Controller v%s", __version__) self.log.info("Mission Pinball Framework Game Engine v%s", __mpfversion__) if (__version__.split('.')[0] != __mpfversion__.split('.')[0] or __version__.split('.')[1] != __mpfversion__.split('.')[1]): self.log.error("MPF MC and MPF Game engines must be same " "major.minor versions. You have MPF v{} and MPF-MC" " v{}".format(__mpfversion__, __version__)) raise ValueError("MPF MC and MPF Game engines must be same " "major.minor versions. You have MPF v{} and MPF-MC" " v{}".format(__mpfversion__, __version__)) super().__init__(**kwargs) self.options = options self.machine_config = config self.log.info("Machine path: %s", machine_path) self.machine_path = machine_path self.clock = Clock # pylint: disable-msg=protected-access self.log.info("Starting clock at %sHz", Clock._max_fps) self._boot_holds = set() self.mpf_path = os.path.dirname(mpf.__file__) self.modes = CaseInsensitiveDict() self.player_list = list() self.player = None self.num_players = 0 self.bcp_client_connected = False self.placeholder_manager = McPlaceholderManager(self) self.settings = McSettingsController(self) self.animation_configs = dict() self.active_slides = dict() self.scriptlets = list() self.register_boot_hold('init') self.displays = CaseInsensitiveDict() self.machine_vars = CaseInsensitiveDict() self.machine_var_monitor = False self.monitors = dict() self.targets = dict() """Dict which contains all the active slide frames in the machine that a slide can target. Will always contain an entry called 'default' which will be used if a slide doesn't specify targeting. """ self.keyboard = None self.physical_dmds = [] self.physical_rgb_dmds = [] self.crash_queue = queue.Queue() self.ticks = 0 self.start_time = 0 self.is_init_done = False if thread_stopper: self.thread_stopper = thread_stopper else: self.thread_stopper = threading.Event() # Core components self.config_validator = ConfigValidator(self) self.events = EventManager(self) self.mode_controller = ModeController(self) create_config_collections(self, self.machine_config['mpf-mc']['config_collections']) ConfigValidator.load_config_spec() self.config_processor = ConfigProcessor(self) self.transition_manager = TransitionManager(self) self._set_machine_path() self._load_font_paths() # Initialize the sound system (must be done prior to creating the AssetManager). # If the sound system is not available, do not load any other sound-related modules. if SoundSystem is None: self.sound_system = None else: self.sound_system = SoundSystem(self) self.asset_manager = ThreadedAssetManager(self) self.bcp_processor = BcpProcessor(self) # Asset classes ImageAsset.initialize(self) VideoAsset.initialize(self) self._initialise_sound_system() self.clock.schedule_interval(self._check_crash_queue, 1) self.events.add_handler("client_connected", self._create_physical_dmds) self.events.add_handler("player_turn_start", self.player_start_turn)
def __init__(self, mpf_path: str, machine_path: str, options: dict): """Initialise machine controller.""" self.log = logging.getLogger("Machine") self.log.info("Mission Pinball Framework Core Engine v%s", __version__) self.log.debug("Command line arguments: %s", options) self.options = options self.log.debug("MPF path: %s", mpf_path) self.mpf_path = mpf_path self.log.info("Machine path: %s", machine_path) self.machine_path = machine_path self.log.debug("Command line arguments: %s", self.options) self.verify_system_info() self._exception = None self._boot_holds = set() self.is_init_done = False self.register_boot_hold('init') self._done = False self.monitors = dict() self.plugins = list() self.scriptlets = list() self.modes = DeviceCollection(self, 'modes', None) self.game = None self.active_debugger = dict() self.machine_vars = CaseInsensitiveDict() self.machine_var_monitor = False self.machine_var_data_manager = None self.thread_stopper = threading.Event() self.delayRegistry = DelayManagerRegistry(self) self.delay = DelayManager(self.delayRegistry) self.crash_queue = queue.Queue() self.config = None self.events = None self.machine_config = None self._set_machine_path() self.config_validator = ConfigValidator(self) self._load_config() self.clock = self._load_clock() self._crash_queue_checker = self.clock.schedule_interval( self._check_crash_queue, 1) self.hardware_platforms = dict() self.default_platform = None self._load_hardware_platforms() self._initialize_credit_string() self._load_core_modules() # order is specified in mpfconfig.yaml # This is called so hw platforms have a chance to register for events, # and/or anything else they need to do with core modules since # they're not set up yet when the hw platforms are constructed. self._initialize_platforms() self._validate_config() self._register_config_players() self._register_system_events() self._load_machine_vars() self._run_init_phases() ConfigValidator.unload_config_spec() self.clear_boot_hold('init')
def set_machine_var(self, name, value, force_events=False): """Set the value of a machine variable. Args: name: String name of the variable you're setting the value for. value: The value you're setting. This can be any Type. force_events: Boolean which will force the event posting, the machine monitor callback, and writing the variable to disk (if it's set to persist). By default these things only happen if the new value is different from the old value. """ if name not in self.machine_vars: self.log.warning( "Received request to set machine_var '%s', but " "that is not a valid machine_var.", name) return prev_value = self.machine_vars[name]['value'] self.machine_vars[name]['value'] = value try: change = value - prev_value except TypeError: change = prev_value != value if change or force_events: if self.machine_vars[name]['persist'] and self.config['mpf'][ 'save_machine_vars_to_disk']: disk_var = CaseInsensitiveDict() disk_var['value'] = value if self.machine_vars[name]['expire_secs']: disk_var['expire'] = self.clock.get_time( ) + self.machine_vars[name]['expire_secs'] self.machine_var_data_manager.save_key(name, disk_var) self.log.debug( "Setting machine_var '%s' to: %s, (prior: %s, " "change: %s)", name, value, prev_value, change) self.events.post('machine_var_' + name, value=value, prev_value=prev_value, change=change) '''event: machine_var_(name) desc: Posted when a machine variable is added or changes value. (Machine variables are like player variables, except they're maintained machine-wide instead of per-player or per-game.) args: value: The new value of this machine variable. prev_value: The previous value of this machine variable, e.g. what it was before the current value. change: If the machine variable just changed, this will be the amount of the change. If it's not possible to determine a numeric change (for example, if this machine variable is a list), then this *change* value will be set to the boolean *True*. ''' if self.machine_var_monitor: for callback in self.monitors['machine_vars']: callback(name=name, value=value, prev_value=prev_value, change=change)
named_rgb_colors = CaseInsensitiveDict( off=(0, 0, 0), aliceblue=(240, 248, 255), antiquewhite=(250, 235, 215), aquamarine=(127, 255, 212), azure=(240, 255, 255), beige=(245, 245, 220), bisque=(255, 228, 196), black=(0, 0, 0), blanchedalmond=(255, 235, 205), blue=(0, 0, 255), blueviolet=(138, 43, 226), brown=(165, 42, 42), burlywood=(222, 184, 135), cadetblue=(95, 158, 160), chartreuse=(127, 255, 0), chocolate=(210, 105, 30), coral=(255, 127, 80), cornflowerblue=(100, 149, 237), cornsilk=(255, 248, 220), crimson=(220, 20, 60), cyan=(0, 255, 255), darkblue=(0, 0, 139), darkcyan=(0, 139, 139), darkgoldenrod=(184, 134, 11), darkgray=(169, 169, 169), darkgreen=(0, 100, 0), darkkhaki=(189, 183, 107), darkmagenta=(139, 0, 139), darkolivegreen=(85, 107, 47), darkorange=(255, 140, 0), darkorchid=(153, 50, 204), darkred=(139, 0, 0), darksalmon=(233, 150, 122), darkseagreen=(143, 188, 143), darkslateblue=(72, 61, 139), darkslategray=(47, 79, 79), darkturquoise=(0, 206, 209), darkviolet=(148, 0, 211), deeppink=(255, 20, 147), deepskyblue=(0, 191, 255), dimgray=(105, 105, 105), dodgerblue=(30, 144, 255), firebrick=(178, 34, 34), floralwhite=(255, 250, 240), forestgreen=(34, 139, 34), gainsboro=(220, 220, 220), ghostwhite=(248, 248, 255), gold=(255, 215, 0), goldenrod=(218, 165, 32), gray=(128, 128, 128), green=(0, 128, 0), greenyellow=(173, 255, 47), honeydew=(240, 255, 240), hotpink=(255, 105, 180), indianred=(205, 92, 92), indigo=(75, 0, 130), ivory=(255, 255, 240), khaki=(240, 230, 140), lavender=(230, 230, 250), lavenderblush=(255, 240, 245), lawngreen=(124, 252, 0), lemonchiffon=(255, 250, 205), lightblue=(173, 216, 230), lightcoral=(240, 128, 128), lightcyan=(224, 255, 255), lightgoldenrodyellow=(250, 250, 210), lightgreen=(144, 238, 144), lightgrey=(211, 211, 211), lightpink=(255, 182, 193), lightsalmon=(255, 160, 122), lightseagreen=(32, 178, 170), lightskyblue=(135, 206, 250), lightslategray=(119, 136, 153), lightsteelblue=(176, 196, 222), lightyellow=(255, 255, 224), lime=(0, 255, 0), limegreen=(50, 205, 50), linen=(250, 240, 230), magenta=(255, 0, 255), maroon=(128, 0, 0), mediumaquamarine=(102, 205, 170), mediumblue=(0, 0, 205), mediumorchid=(186, 85, 211), mediumpurple=(147, 112, 219), mediumseagreen=(60, 179, 113), mediumslateblue=(123, 104, 238), mediumspringgreen=(0, 250, 154), mediumturquoise=(72, 209, 204), mediumvioletred=(199, 21, 133), midnightblue=(25, 25, 112), mintcream=(245, 255, 250), mistyrose=(255, 228, 225), moccasin=(255, 228, 181), navajowhite=(255, 222, 173), navy=(0, 0, 128), oldlace=(253, 245, 230), olive=(128, 128, 0), olivedrab=(107, 142, 35), orange=(255, 165, 0), orangered=(255, 69, 0), orchid=(218, 112, 214), palegoldenrod=(238, 232, 170), palegreen=(152, 251, 152), paleturquoise=(175, 238, 238), palevioletred=(219, 112, 147), papayawhip=(255, 239, 213), peachpuff=(255, 218, 185), peru=(205, 133, 63), pink=(255, 192, 203), plum=(221, 160, 221), powderblue=(176, 224, 230), purple=(128, 0, 128), rebeccapurple=(102, 51, 153), red=(255, 0, 0), rosybrown=(188, 143, 143), royalblue=(65, 105, 225), saddlebrown=(139, 69, 19), salmon=(250, 128, 114), sandybrown=(244, 164, 96), seagreen=(46, 139, 87), seashell=(255, 245, 238), sienna=(160, 82, 45), silver=(192, 192, 192), skyblue=(135, 206, 235), slateblue=(106, 90, 205), slategray=(112, 128, 144), snow=(255, 250, 250), springgreen=(0, 255, 127), steelblue=(70, 130, 180), tan=(210, 180, 140), teal=(0, 128, 128), thistle=(216, 191, 216), tomato=(255, 99, 71), turquoise=(64, 224, 208), violet=(238, 130, 238), wheat=(245, 222, 179), white=(255, 255, 255), whitesmoke=(245, 245, 245), yellow=(255, 255, 0), yellowgreen=(154, 205, 50), )
def __init__(self, mc): """Initialise sound system.""" self.mc = mc self.log = logging.getLogger('SoundSystem') self._initialized = False self.audio_interface = None self.config = dict() self.sound_events = dict() self.tracks = CaseInsensitiveDict() self.clock_event = None self.log.debug("Loading the Sound System") # Load configuration for sound system if 'sound_system' not in self.mc.machine_config: self.log.info("SoundSystem: Using default 'sound_system' settings") self.config = dict() else: self.config = self.mc.machine_config['sound_system'] # TODO: Use config spec validator # Validate configuration and provide default values where needed if 'enabled' not in self.config: self.config['enabled'] = DEFAULT_AUDIO_ENABLED # If the sound system has been disabled, abort initialization if not self.config['enabled']: self.log.debug( "SoundSystem: The sound system has been disabled in " "the configuration file (enabled: False). No audio " "features will be available.") return if 'buffer' not in self.config or self.config['buffer'] == 'auto': self.config['buffer'] = DEFAULT_AUDIO_BUFFER_SAMPLE_SIZE elif not AudioInterface.power_of_two(self.config['buffer']): self.log.warning( "SoundSystem: The buffer setting is not a power of " "two. Default buffer size will be used.") self.config['buffer'] = DEFAULT_AUDIO_BUFFER_SAMPLE_SIZE if 'frequency' not in self.config or self.config['frequency'] == 'auto': self.config['frequency'] = DEFAULT_SAMPLE_RATE if 'channels' not in self.config: self.config['channels'] = DEFAULT_AUDIO_CHANNELS if 'master_volume' not in self.config: self.config['master_volume'] = DEFAULT_MASTER_VOLUME # Initialize audio interface library (get audio output) try: self.audio_interface = AudioInterface( rate=self.config['frequency'], channels=self.config['channels'], buffer_samples=self.config['buffer']) except AudioException: self.log.error("Could not initialize the audio interface. " "Audio features will not be available.") self.audio_interface = None return # Setup tracks in audio system (including initial volume levels) if 'tracks' in self.config: for track_name, track_config in self.config['tracks'].items(): self._create_track(track_name, track_config) else: self._create_track('default') self.log.info( "No audio tracks are specified in your machine config file. " "a track named 'default' has been created.") # Set initial master volume level self.master_volume = self.config['master_volume'] # Establish machine tick function callback (will process internal audio events) self.clock_event = Clock.schedule_interval(self.tick, 0) # Start audio engine processing self.audio_interface.enable() self._initialized = True self.mc.events.add_handler("master_volume_increase", self.master_volume_increase) self.mc.events.add_handler("master_volume_decrease", self.master_volume_decrease) self.mc.events.add_handler("shutdown", self.shutdown) self.mc.events.add_handler("client_connected", self._send_volume, -1)