예제 #1
0
    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]]
예제 #2
0
    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
예제 #3
0
    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)
예제 #4
0
    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
예제 #5
0
    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)
예제 #6
0
파일: mode.py 프로젝트: ngksternhagen/mpf
    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
예제 #7
0
    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)
예제 #8
0
    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()
예제 #9
0
    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()
예제 #10
0
파일: utils.py 프로젝트: vgrillot/mpf-mc
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
예제 #11
0
    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
예제 #12
0
    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)
예제 #13
0
    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
예제 #14
0
    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
예제 #15
0
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
예제 #16
0
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
예제 #17
0
파일: mc.py 프로젝트: elliotstarks/mpf-mc
    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)
예제 #18
0
    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')
예제 #19
0
    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)
예제 #20
0
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),
)
예제 #21
0
    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)