def configure(self, config=None): """Performs the actual configuration of the ball device based on the dictionary that was passed to it. Args: config: Python dictionary which holds the configuration settings. """ # Merge in any new changes that were just passed if config: self.config.update(config) self.log.debug("Configuring device with: %s", self.config) # convert delay strings to ms ints if self.config['exit_count_delay']: self.config['exit_count_delay'] = \ Timing.string_to_ms(self.config['exit_count_delay']) if self.config['entrance_count_delay']: self.config['entrance_count_delay'] = \ Timing.string_to_ms(self.config['entrance_count_delay']) # Register for events # Look for eject requests for this device self.machine.events.add_handler( 'balldevice_' + self.name + '_ball_eject_request', self.eject)
def configure(self, config=None): """Performs the actual configuration of the ball device based on the dictionary that was passed to it. Args: config: Python dictionary which holds the configuration settings. """ # Merge in any new changes that were just passed if config: self.config.update(config) self.log.debug("Configuring device with: %s", self.config) # convert delay strings to ms ints if self.config['exit_count_delay']: self.config['exit_count_delay'] = \ Timing.string_to_ms(self.config['exit_count_delay']) if self.config['entrance_count_delay']: self.config['entrance_count_delay'] = \ Timing.string_to_ms(self.config['entrance_count_delay']) # Register for events # Look for eject requests for this device self.machine.events.add_handler('balldevice_' + self.name + '_ball_eject_request', self.eject)
def __init__(self, machine, name, config): self.log = logging.getLogger('HitCounter.' + name) self.log.debug("Creating HitCounter LogicBlock") super(HitCounter, self).__init__(machine, name, config) self.delay = DelayManager() self.num_hits = 0 self.ignore_hits = False if 'trigger_events' not in self.config: return # Not much point to continue here else: self.config['trigger_events'] = self.machine.string_to_list( self.config['trigger_events']) if 'event_when_hit' not in self.config: self.config['event_when_hit'] = ('eventtrigger_' + self.name + '_hit') if 'hits_to_complete' not in self.config: self.config['hits_to_complete'] = 1 if 'multiple_hit_window' not in self.config: self.config['multiple_hit_window'] = None else: self.config['multiple_hit_window'] = Timing.string_to_ms( self.config['multiple_hit_window']) if 'settle_time' not in self.config: self.config['settle_time'] = None else: self.config['settle_time'] = Timing.string_to_ms( self.config['settle_time'])
def tick(self): """Method that runs as a task """ while self.active: for coil in self.ball_search_coils: self.pop_coil(coil) yield Timing.secs(self.machine.config['ballsearch']\ ['secs between ball search coils']) yield Timing.secs(self.machine.config['ballsearch']\ ['secs between ball search rounds'])
def tick(self): """Method that runs as a task """ while self.active: for coil in self.ball_search_coils: self.pop_coil(coil) yield Timing.secs(self.machine.config['BallSearch']\ ['Secs between ball search coils']) yield Timing.secs(self.machine.config['BallSearch']\ ['Secs between ball search rounds'])
def _initialize_switches(self): self.update_switches_from_hw() for switch in self.machine.switches: # Populate self.switches self.set_state(switch.name, switch.state, reset_time=True) # Populate self.registered_switches self.registered_switches[switch.name + '-0'] = list() self.registered_switches[switch.name + '-1'] = list() if self.machine.config['mpf']['auto_create_switch_events']: switch.activation_events.add( self.machine.config['mpf']['switch_event_active'].replace( '%', switch.name)) switch.deactivation_events.add( self.machine.config['mpf'][ 'switch_event_inactive'].replace( '%', switch.name)) if 'activation_events' in switch.config: for event in Config.string_to_lowercase_list( switch.config['activation_events']): if "|" in event: ev_name, ev_time = event.split("|") self.add_switch_handler( switch_name=switch.name, callback=self.machine.events.post, state=1, ms=Timing.string_to_ms(ev_time), callback_kwargs={'event': ev_name} ) else: switch.activation_events.add(event) if 'deactivation_events' in switch.config: for event in Config.string_to_lowercase_list( switch.config['deactivation_events']): if "|" in event: ev_name, ev_time = event.split("|") self.add_switch_handler( switch_name=switch.name, callback=self.machine.events.post, state=0, ms=Timing.string_to_ms(ev_time), callback_kwargs={'event': ev_name} ) else: switch.deactivation_events.add(event)
def _initialize_switches(self): self.update_switches_from_hw() for switch in self.machine.switches: # Populate self.switches self.set_state(switch.name, switch.state, reset_time=True) # Populate self.registered_switches self.registered_switches[switch.name + '-0'] = list() self.registered_switches[switch.name + '-1'] = list() if self.machine.config['mpf']['auto_create_switch_events']: switch.activation_events.add( self.machine.config['mpf']['switch_event_active'].replace( '%', switch.name)) switch.deactivation_events.add( self.machine.config['mpf'] ['switch_event_inactive'].replace('%', switch.name)) if 'activation_events' in switch.config: for event in Util.string_to_lowercase_list( switch.config['activation_events']): if "|" in event: ev_name, ev_time = event.split("|") self.add_switch_handler( switch_name=switch.name, callback=self.machine.events.post, state=1, ms=Timing.string_to_ms(ev_time), callback_kwargs={'event': ev_name}) else: switch.activation_events.add(event) if 'deactivation_events' in switch.config: for event in Util.string_to_lowercase_list( switch.config['deactivation_events']): if "|" in event: ev_name, ev_time = event.split("|") self.add_switch_handler( switch_name=switch.name, callback=self.machine.events.post, state=0, ms=Timing.string_to_ms(ev_time), callback_kwargs={'event': ev_name}) else: switch.deactivation_events.add(event)
def __init__(self, mpfdisplay, machine, priority, mode, slide_a, slide_b, duration='1s', **kwargs): super(Transition, self).__init__(mpfdisplay=mpfdisplay, machine=machine, priority=priority, mode=mode) self.slide_a = slide_a self.slide_b = slide_b self.priority = slide_b.priority self.duration = Timing.string_to_secs(duration) self.active_transition = True self.slide_a.active_transition = True self.slide_b.active_transition = True self.name = str(slide_a.name) + "_transition_" + str(slide_b.name) self.start_time = time.time() self.end_time = self.start_time + self.duration # mark both slides as active self.slide_a.active = True self.slide_b.active = True # Need to set the initial surface of the transition slide to the # existing slide's surface since the transition slide will be active self.surface.blit(self.slide_a.surface, (0, 0))
def __init__(self, machine, name, config, collection=None): self.log = logging.getLogger('Diverter.' + name) super(Diverter, self).__init__(machine, name, config, collection) self.delay = DelayManager() # configure defaults: if 'type' not in self.config: self.config['type'] = 'pulse' # default to pulse to not fry coils if 'timeout' not in self.config: self.config['timeout'] = 0 if 'activation_switch' not in self.config: self.config['activation_switch'] = None if 'disable_switch' not in self.config: self.config['disable_switch'] = None if 'target_when_enabled' not in self.config: self.config['target_when_enabled'] = None # todo if 'target_when_disabled' not in self.config: self.config['target_when_disabled'] = None # todo # convert the timeout to ms self.config['timeout'] = Timing.string_to_ms(self.config['timeout']) # register for events for event in self.config['enable_events']: self.machine.events.add_handler(event, self.enable) for event in self.config['disable_events']: self.machine.events.add_handler(event, self.disable)
def _do_step(self): this_step = self.step_list[self.current_step] self.log.info("Switch: %s, Action: %s", this_step['switch'], this_step['action']) # send this step's switches if this_step['action'] == 'activate': self.machine.switch_controller.process_switch( this_step['switch'], state=1, logical=True) elif this_step['action'] == 'deactivate': self.machine.switch_controller.process_switch( this_step['switch'], state=0, logical=True) elif this_step['action'] == 'hit': self._hit(this_step['switch']) # inc counter if self.current_step < len(self.step_list)-1: self.current_step += 1 # schedule next step self.delay.add(name='switch_player_next_step', ms=Timing.string_to_ms(self.step_list[self.current_step]['time']), callback=self._do_step)
def __init__(self, machine, name, config, priority): """SequenceShot is where you need certain switches to be hit in the right order, possibly within a time limit. Subclass of `Shot` Args: machine: The MachineController object name: String name of this shot. config: Dictionary that holds the configuration for this shot. """ super(SequenceShot, self).__init__(machine, name, config, priority) self.delay = DelayManager() self.progress_index = 0 """Tracks how far along through this sequence the current shot is.""" # convert our switches config to a list if 'switches' in self.config: self.config['switches'] = \ Config.string_to_list(self.config['switches']) # convert our timout to ms if 'time' in self.config: self.config['time'] = Timing.string_to_ms(self.config['time']) else: self.config['time'] = 0 self.active_delay = False self.enable()
def _do_step(self): this_step = self.step_list[self.current_step] self.log.debug("Switch: %s, Action: %s", this_step['switch'], this_step['action']) # send this step's switches if this_step['action'] == 'activate': self.machine.switch_controller.process_switch(this_step['switch'], state=1, logical=True) elif this_step['action'] == 'deactivate': self.machine.switch_controller.process_switch(this_step['switch'], state=0, logical=True) elif this_step['action'] == 'hit': self._hit(this_step['switch']) # inc counter if self.current_step < len(self.step_list) - 1: self.current_step += 1 # schedule next step self.delay.add(name='switch_player_next_step', ms=Timing.string_to_ms( self.step_list[self.current_step]['time']), callback=self._do_step)
def __init__(self, mpfdisplay, machine, slide_a, slide_b, duration='1s', **kwargs): super(Transition, self).__init__(mpfdisplay, machine, name=self.name) self.slide_a = slide_a self.slide_b = slide_b self.priority = slide_b.priority self.duration = Timing.string_to_secs(duration) # Need to make sure both the slides have rendered in case they're new self.slide_b.update() self.slide_a.update() self.start_time = time.time() self.end_time = self.start_time + self.duration # mark both slides as active self.slide_a.active = True self.slide_b.active = True
def _start_event_callback(self): if ('time' in self.step_list[self.current_step] and self.step_list[self.current_step]['time'] > 0): self.delay.add(name='switch_player_next_step', ms=Timing.string_to_ms(self.step_list[self.current_step]['time']), callback=self._do_step)
def _create_events(self, ev_name, ev_type, delay, callback): self.log.debug("Creating %s_event handler for event '%s' with delay " "'%s'", ev_type, ev_name, delay) self.machine.events.add_handler(event=ev_name, handler=self._action_event_handler, callback=callback, ms_delay=Timing.string_to_ms(delay))
def __init__(self, machine, mode, name, config): self.machine = machine self.mode = mode self.name = name self.config = config self.tick_var = self.mode.name + '_' + self.name + '_tick' self.mode.player[self.tick_var] = 0 self.running = False self.start_value = 0 self.restart_on_complete = False self._ticks = 0 self.end_value = 0 self.max_value = None self.direction = 'up' self.tick_secs = 1 self.timer = None self.bcp = False self.event_keys = set() self.delay = DelayManager() if 'start_value' in self.config: self.start_value = self.config['start_value'] else: self.start_value = 0 if 'start_running' in self.config and self.config['start_running']: self.running = True if 'end_value' in self.config: self.end_value = self.config['end_value'] if 'control_events' in self.config and self.config['control_events']: if type(self.config['control_events']) is dict: self.config['control_events'] = [self.config['control_events']] else: self.config['control_events'] = list() if 'direction' in self.config and self.config['direction'] == 'down': self.direction = 'down' if 'tick_interval' in self.config: self.tick_secs = Timing.string_to_secs(self.config['tick_interval']) if 'max_value' in self.config: self.max_value = self.config['max_value'] if ('restart_on_complete' in self.config and self.config['restart_on_complete']): self.restart_on_complete = True if 'bcp' in self.config and self.config['bcp']: self.bcp = True self.mode.player[self.tick_var] = self.start_value self._setup_control_events(self.config['control_events'])
def _start_event_callback(self): if ('time' in self.step_list[self.current_step] and self.step_list[self.current_step]['time'] > 0): self.delay.add(name='switch_player_next_step', ms=Timing.string_to_ms( self.step_list[self.current_step]['time']), callback=self._do_step)
def _start_event_callback(self): if "time" in self.step_list[self.current_step] and self.step_list[self.current_step]["time"] > 0: self.delay.add( name="switch_player_next_step", ms=Timing.string_to_ms(self.step_list[self.current_step]["time"]), callback=self._do_step, )
def _create_events(self, ev_name, ev_type, delay, callback): self.log.debug( "Creating %s_event handler for event '%s' with delay " "'%s'", ev_type, ev_name, delay) self.machine.events.add_handler(event=ev_name, handler=self._action_event_handler, callback=callback, ms_delay=Timing.string_to_ms(delay))
def validate_config_item(spec, item='item not in config!@#'): default = 'default required!@#' if '|' in spec: item_type, default = spec.split('|') if type(default) is str and default.lower() == 'none': default = None else: item_type = spec if item == 'item not in config!@#': if default == 'default required!@#': log.error( 'Required setting missing from config file. Run with ' 'verbose logging and look for the last ' 'ConfigProcessor entry above this line to see where ' 'the problem is.') sys.exit() else: item = default if item_type == 'list': return Config.string_to_list(item) elif item_type == 'int': return int(item) elif item_type == 'float': return float(item) elif item_type == 'string': return str(item) elif item_type == 'boolean': if type(item) is bool: return item else: return item.lower() in ('yes', 'true') elif item_type == 'ms': return Timing.string_to_ms(item) elif item_type == 'secs': return Timing.string_to_secs(item) elif item_type == 'list_of_lists': return Config.list_of_lists(item)
def validate_config_item(spec, item='item not in config!@#'): default = 'default required!@#' if '|' in spec: item_type, default = spec.split('|') if type(default) is str and default.lower() == 'none': default = None else: item_type = spec if item == 'item not in config!@#': if default == 'default required!@#': log.error('Required setting missing from config file. Run with ' 'verbose logging and look for the last ' 'ConfigProcessor entry above this line to see where ' 'the problem is.') sys.exit() else: item = default if item_type == 'list': return Config.string_to_list(item) elif item_type == 'int': return int(item) elif item_type == 'float': return float(item) elif item_type == 'string': return str(item) elif item_type == 'boolean': if type(item) is bool: return item else: return item.lower() in ('yes', 'true') elif item_type == 'ms': return Timing.string_to_ms(item) elif item_type == 'secs': return Timing.string_to_secs(item) elif item_type == 'list_of_lists': return Config.list_of_lists(item)
def configure(self): """Configures the shot.""" # convert our switches config to a list if 'Switches' in self.config: self.config['Switches'] = \ self.machine.string_to_list(self.config['Switches']) # convert our timout to ms if 'Time' in self.config: self.config['Time'] = Timing.string_to_ms(self.config['Time']) else: self.config['Time'] = 0
def schedule_removal(self, removal_time=None): """Schedules this slide to automatically be removed. Args: removal_time: MPF time string of when this slide should be removed. If no time is specified, the slide's existing removal time is used. If the slide has no existing time, the slide will not be removed. """ if removal_time: self.expire_ms = Timing.string_to_ms(removal_time) if self.expire_ms: self.machine.display.delay.add(name=self.name + "_expiration", ms=self.expire_ms, callback=self.remove)
def schedule_removal(self, removal_time=None): """Schedules this slide to automatically be removed. Args: removal_time: MPF time string of when this slide should be removed. If no time is specified, the slide's existing removal time is used. If the slide has no existing time, the slide will not be removed. """ if removal_time: self.expire_ms = Timing.string_to_ms(removal_time) if self.expire_ms: self.machine.display.delay.add(name=self.name + '_expiration', ms=self.expire_ms, callback=self.remove)
def __init__(self, mpfdisplay, machine, slide_a, slide_b, duration="1s", **kwargs): super(Transition, self).__init__(mpfdisplay, machine, name=self.name) self.slide_a = slide_a self.slide_b = slide_b self.priority = slide_b.priority self.duration = Timing.string_to_secs(duration) # Need to make sure both the slides have rendered in case they're new self.slide_b.update() self.slide_a.update() self.start_time = time.time() self.end_time = self.start_time + self.duration # mark both slides as active self.slide_a.active = True self.slide_b.active = True
def schedule_deactivation(self, time=None): """Schedules a delay to deactivate this diverter. Args: time: The MPF string time of how long you'd like the delay before deactivating the diverter. Default is None which means it uses the 'activation_time' setting configured for this diverter. If there is no 'activation_time' setting and no delay is passed, it will disable the diverter immediately. """ if time is not None: delay = Timing.string_to_ms(time) elif self.config['activation_time']: delay = self.config['activation_time'] if delay: self.delay.add('disable_held_coil', delay, self.disable_held_coil) else: self.disable_held_coil()
def _do_step(self): this_step = self.step_list[self.current_step] self.log.debug("Switch: %s, Action: %s", this_step["switch"], this_step["action"]) # send this step's switches if this_step["action"] == "activate": self.machine.switch_controller.process_switch(this_step["switch"], state=1, logical=True) elif this_step["action"] == "deactivate": self.machine.switch_controller.process_switch(this_step["switch"], state=0, logical=True) elif this_step["action"] == "hit": self._hit(this_step["switch"]) # inc counter if self.current_step < len(self.step_list) - 1: self.current_step += 1 # schedule next step self.delay.add( name="switch_player_next_step", ms=Timing.string_to_ms(self.step_list[self.current_step]["time"]), callback=self._do_step, )
def validate_item(self, item, validator, validation_failure_info): try: if item.lower() == 'none': item = None except AttributeError: pass if ':' in validator: validator = validator.split(':') # item could be str, list, or list of dicts item = Util.event_config_to_dict(item) return_dict = dict() for k, v in item.iteritems(): return_dict[self.validate_item(k, validator[0], validation_failure_info)] = ( self.validate_item(v, validator[1], validation_failure_info) ) item = return_dict elif '%' in validator: if type(item) is str: try: item = eval(validator.replace('%', "'" + item + "'")) except KeyError: self.validation_error(item, validation_failure_info) else: item = None elif validator == 'str': if item is not None: item = str(item) else: item = None elif validator == 'float': try: item = float(item) except (TypeError, ValueError): # TODO error pass elif validator == 'int': try: item = int(item) except (TypeError, ValueError): # TODO error pass elif validator in ('bool', 'boolean'): if type(item) is str: if item.lower() in ['false', 'f', 'no', 'disable', 'off']: item = False elif not item: item = False else: item = True elif validator == 'ms': item = Timing.string_to_ms(item) elif validator == 'secs': item = Timing.string_to_secs(item) elif validator == 'ticks': item = Timing.string_to_ticks(item) elif validator == 'ticks_int': item = int(Timing.string_to_ticks(item)) else: self.log.error("Invalid Validator '%s' in config spec %s:%s", validator, validation_failure_info[0][0], validation_failure_info[1]) sys.exit() return item
class MediaController(object): def __init__(self, options): self.options = options self.log = logging.getLogger("MediaController") self.log.info("Media Controller Version %s", version.__version__) self.log.info("Backbox Control Protocol Version %s", version.__bcp_version__) self.log.info("Config File Version %s", version.__config_version__) python_version = sys.version_info self.log.info("Python version: %s.%s.%s", python_version[0], python_version[1], python_version[2]) self.log.info("Platform: %s", sys.platform) self.log.info("Python executable location: %s", sys.executable) self.log.info("32-bit Python? %s", sys.maxsize < 2**32) self.config = dict() self.done = False # todo self.machine_path = None self.asset_managers = dict() self.num_assets_to_load = 0 self.window = None self.window_manager = None self.pygame = False self.pygame_requested = False self.registered_pygame_handlers = dict() self.pygame_allowed_events = list() self.socket_thread = None self.receive_queue = Queue.Queue() self.sending_queue = Queue.Queue() self.crash_queue = Queue.Queue() self.game_modes = CaseInsensitiveDict() self.player_list = list() self.player = None self.HZ = 0 self.next_tick_time = 0 self.secs_per_tick = 0 Task.Create(self._check_crash_queue) self.bcp_commands = {'hello': self.bcp_hello, 'goodbye': self.bcp_goodbye, 'reset': self.reset, 'mode_start': self.bcp_mode_start, 'mode_stop': self.bcp_mode_stop, 'error': self.bcp_error, 'ball_start': self.bcp_ball_start, 'ball_end': self.bcp_ball_end, 'game_start': self.bcp_game_start, 'game_end': self.bcp_game_end, 'player_added': self.bcp_player_add, 'player_variable': self.bcp_player_variable, 'player_score': self.bcp_player_score, 'player_turn_start': self.bcp_player_turn_start, 'attract_start': self.bcp_attract_start, 'attract_stop': self.bcp_attract_stop, 'trigger': self.bcp_trigger, 'switch': self.bcp_switch, 'get': self.bcp_get, 'set': self.bcp_set, 'config': self.bcp_config, 'timer': self.bcp_timer } # load the MPF config & machine defaults self.config = ( Config.load_config_yaml(config=self.config, yaml_file=self.options['mcconfigfile'])) # Find the machine_files location. If it starts with a forward or # backward slash, then we assume it's from the mpf root. Otherwise we # assume it's from the subfolder location specified in the # mpfconfigfile location if (options['machinepath'].startswith('/') or options['machinepath'].startswith('\\')): machine_path = options['machinepath'] else: machine_path = os.path.join(self.config['mediacontroller']['paths'] ['machine_files'], options['machinepath']) self.machine_path = os.path.abspath(machine_path) # Add the machine folder to our path so we can import modules from it sys.path.append(self.machine_path) self.log.info("Machine folder: %s", machine_path) # Now find the config file location. Same as machine_file with the # slash uses to specify an absolute path if (options['configfile'].startswith('/') or options['configfile'].startswith('\\')): config_file = options['configfile'] else: if not options['configfile'].endswith('.yaml'): options['configfile'] += '.yaml' config_file = os.path.join(self.machine_path, self.config['mediacontroller']['paths'] ['config'], options['configfile']) self.log.info("Base machine config file: %s", config_file) # Load the machine-specific config self.config = Config.load_config_yaml(config=self.config, yaml_file=config_file) mediacontroller_config_spec = ''' exit_on_disconnect: boolean|True port: int|5050 ''' self.config['mediacontroller'] = ( Config.process_config(mediacontroller_config_spec, self.config['mediacontroller'])) self.events = EventManager(self) self.timing = Timing(self) # Load the media controller modules self.config['mediacontroller']['modules'] = ( self.config['mediacontroller']['modules'].split(' ')) for module in self.config['mediacontroller']['modules']: self.log.info("Loading module: %s", module) module_parts = module.split('.') exec('self.' + module_parts[0] + '=' + module + '(self)') # todo there's probably a more pythonic way to do this, and I know # exec() is supposedly unsafe, but meh, if you have access to put # malicious files in the system folder then you have access to this # code too. self.start_socket_thread() self.events.post("init_phase_1") self.events.post("init_phase_2") self.events.post("init_phase_3") self.events.post("init_phase_4") self.events.post("init_phase_5") self.reset() def _check_crash_queue(self): try: crash = self.crash_queue.get(block=False) except Queue.Empty: yield 1000 else: self.log.critical("MPF Shutting down due to child thread crash") self.log.critical("Crash details: %s", crash) self.done = True def reset(self, **kwargs): """Processes an incoming BCP 'reset' command.""" self.player = None self.player_list = list() self.events.post('mc_reset_phase_1') self.events.post('mc_reset_phase_2') self.events.post('mc_reset_phase_3') def get_window(self): """ Returns a reference to the onscreen display window. This method will set up a window if one doesn't exist yet. This method exists because there are several different modules and plugins which may want to use a window, but we don't know which combinations might be used, so we centralize the creation and management of an onscreen window here. """ if not self.window: self.window_manager = window.WindowManager(self) self.window = self.window_manager.window return self.window def request_pygame(self): """Called by a module to let the system know it would like to use Pygame. We centralize the requests instead of letting each module do their own pygame.init() so we get it in one place and can get everthing initialized in the right order. Returns: True or False, depending on whether pygame is available or not. """ if pygame and not self.pygame_requested: self.events.add_handler('init_phase_3', self._pygame_init) self.pygame_requested = True return True else: return False def _pygame_init(self): # performs the actual pygame initialization if not pygame: self.log.critical("Pygame is needed but not available. Please " "install Pygame and try again.") raise Exception("Pygame is needed but not available. Please install" " Pygame and try again.") if not self.pygame: self.log.debug("Initializing Pygame, version %s", pygame.version.ver) pygame.init() self.pygame = True self.events.add_handler('timer_tick', self.get_pygame_events, priority=1000) self.events.post('pygame_initialized') def register_pygame_handler(self, event, handler): """Registers a method to be a handler for a certain type of Pygame event. Args: event: A string of the Pygame event name you're registering this handler for. handler: A method that will be called when this Pygame event is posted. """ if event not in self.registered_pygame_handlers: self.registered_pygame_handlers[event] = set() self.registered_pygame_handlers[event].add(handler) self.pygame_allowed_events.append(event) self.log.debug("Adding Window event handler. Event:%s, Handler:%s", event, handler) pygame.event.set_allowed(self.pygame_allowed_events) def get_pygame_events(self): """Gets (and dispatches) Pygame events. Automatically called every machine loop via the timer_tick event. """ for event in pygame.event.get(): if event.type in self.registered_pygame_handlers: for handler in self.registered_pygame_handlers[event.type]: if (event.type == pygame.KEYDOWN or event.type == pygame.KEYUP): handler(event.key, event.mod) else: handler() def _process_command(self, bcp_command, **kwargs): self.log.debug("Processing command: %s %s", bcp_command, kwargs) try: self.bcp_commands[bcp_command](**kwargs) except KeyError: self.log.warning("Received invalid BCP command: %s", bcp_command) self.send('error', message='invalid command', command=bcp_command) def send(self, bcp_command, callback=None, **kwargs): """Sends a BCP command to the connected pinball controller. Args: bcp_command: String of the BCP command name. callback: Optional callback method that will be called when the command is sent. **kwargs: Optional additional kwargs will be added to the BCP command string. """ self.sending_queue.put(bcp.encode_command_string(bcp_command, **kwargs)) if callback: callback() def send_dmd_frame(self, data): """Sends a DMD frame to the BCP client. Args: data: A 4096-length raw byte string. """ dmd_string = 'dmd_frame?' + data self.sending_queue.put(dmd_string) def _timer_init(self): self.HZ = 30 self.next_tick_time = time.time() self.secs_per_tick = 1.0 / self.HZ def timer_tick(self): """Called by the platform each machine tick based on self.HZ""" self.timing.timer_tick() # notifies the timing module self.events.post('timer_tick') # sends the timer_tick system event Task.timer_tick() # notifies tasks DelayManager.timer_tick() def run(self): """Main run loop.""" self._timer_init() self.log.info("Starting the run loop at %sHz", self.HZ) start_time = time.time() loops = 0 secs_per_tick = self.secs_per_tick self.next_tick_time = time.time() try: while self.done is False: time.sleep(0.001) self.get_from_queue() if self.next_tick_time <= time.time(): # todo change this self.timer_tick() self.next_tick_time += secs_per_tick loops += 1 self._do_shutdown() self.log.info("Target loop rate: %s Hz", self.HZ) self.log.info("Actual loop rate: %s Hz", loops / (time.time() - start_time)) except KeyboardInterrupt: self.shutdown() def shutdown(self): """Shuts down and exits the media controller. This method will also send the BCP 'goodbye' command to any connected clients. """ self.socket_thread.stop() def _do_shutdown(self): if self.pygame: pygame.quit() def socket_thread_stopped(self): """Notifies the media controller that the socket thread has stopped.""" self.done = True def start_socket_thread(self): """Starts the BCPServer socket thread.""" self.socket_thread = BCPServer(self, self.receive_queue, self.sending_queue) self.socket_thread.daemon = True self.socket_thread.start() def get_from_queue(self): """Gets and processes all queued up incoming BCP commands.""" while not self.receive_queue.empty(): cmd, kwargs = bcp.decode_command_string( self.receive_queue.get(False)) self._process_command(cmd, **kwargs) def bcp_hello(self, **kwargs): """Processes an incoming BCP 'hello' command.""" try: if LooseVersion(kwargs['version']) == ( LooseVersion(version.__bcp_version__)): self.send('hello', version=version.__bcp_version__) else: self.send('hello', version='unknown protocol version') except: self.log.warning("Received invalid 'version' parameter with " "'hello'") def bcp_goodbye(self, **kwargs): """Processes an incoming BCP 'goodbye' command.""" if self.config['mediacontroller']['exit_on_disconnect']: self.socket_thread.sending_thread.stop() sys.exit() def bcp_mode_start(self, name=None, priority=0, **kwargs): """Processes an incoming BCP 'mode_start' command.""" if not name: return #todo raise error if name in self.game_modes: self.game_modes[name].start(priority=priority) def bcp_mode_stop(self, name, **kwargs): """Processes an incoming BCP 'mode_stop' command.""" if not name: return #todo raise error if name in self.game_modes: self.game_modes[name].stop() def bcp_error(self, **kwargs): """Processes an incoming BCP 'error' command.""" self.log.warning('Received error command from client') def bcp_ball_start(self, **kwargs): """Processes an incoming BCP 'ball_start' command.""" self.events.post('ball_started', **kwargs) def bcp_ball_end(self, **kwargs): """Processes an incoming BCP 'ball_end' command.""" self.events.post('ball_ended', **kwargs) def bcp_game_start(self, **kargs): """Processes an incoming BCP 'game_start' command.""" self.bcp_player_add(number=1) self.bcp_player_turn_start(player=1) self.events.post('game_started', **kargs) def bcp_game_end(self, **kwargs): """Processes an incoming BCP 'game_end' command.""" self.player = None self.events.post('game_ended', **kwargs) def bcp_player_add(self, number, **kwargs): """Processes an incoming BCP 'player_add' command.""" if number > len(self.player_list): new_player = Player(self) self.player_list.append(new_player) new_player.score = 0 self.events.post('player_add_success', num=number) def bcp_player_variable(self, name, value, prev_value, change, **kwargs): """Processes an incoming BCP 'player_variable' command.""" if self.player: self.player[name] = value def bcp_player_score(self, value, prev_value, change, **kwargs): """Processes an incoming BCP 'player_score' command.""" if self.player: self.player['score'] = int(value) def bcp_attract_start(self, **kwargs): """Processes an incoming BCP 'attract_start' command.""" self.events.post('machineflow_Attract_start') def bcp_attract_stop(self, **kwargs): self.events.post('machineflow_Attract_stop') """Processes an incoming BCP 'attract_stop' command.""" def bcp_player_turn_start(self, player, **kwargs): """Processes an incoming BCP 'player_turn_start' command.""" if ((self.player and self.player.number != player) or not self.player): self.player = self.player_list[int(player)-1] def bcp_trigger(self, name, **kwargs): """Processes an incoming BCP 'trigger' command.""" blocked_event_prefixes = ('player_', 'machinemode_', ) blocked_events = ('ball_started', 'ball_ended', 'game_started', 'game_ended', ) if not (name.startswith(blocked_event_prefixes) and name in blocked_events): self.events.post(name, **kwargs) def bcp_switch(self, name, state, **kwargs): """Processes an incoming BCP 'switch' command.""" if int(state): self.events.post('switch_' + name + '_active') else: self.events.post('switch_' + name + '_inactive') def bcp_get(self, **kwargs): """Processes an incoming BCP 'get' command. Note that this media controller doesn't implement the 'get' command at this time, but it's included here for completeness since the 'get' command is part of the BCP 1.0 specification so we don't want to return an error if we receive an incoming 'get' command. """ pass def bcp_set(self, **kwargs): """Processes an incoming BCP 'set' command. Note that this media controller doesn't implement the 'set' command at this time, but it's included here for completeness since the 'set' command is part of the BCP 1.0 specification so we don't want to return an error if we receive an incoming 'set' command. """ pass def bcp_config(self, **kwargs): """Processes an incoming BCP 'config' command.""" for k, v in kwargs.iteritems(): if k.startswith('volume_'): self.bcp_set_volume(track=k.split('volume_')[1], value=v) def bcp_timer(self, name, action, **kwargs): """Processes an incoming BCP 'config' command.""" self.events.post('timer_' + name + '_' + action, **kwargs) def bcp_set_volume(self, track, value): """Sets the volume based on an incoming BCP 'config' command. Args: track: String name of the track the volume will set. value: Float between 0 and 1 which represents the volume level to set. Note: At this time only the master volume can be set with this method. """ if track == 'master': self.sound.set_volume(value)
def validate_item(self, item, validator, validation_failure_info): try: if item.lower() == 'none': item = None except AttributeError: pass if ':' in validator: validator = validator.split(':') # item could be str, list, or list of dicts item = Util.event_config_to_dict(item) return_dict = dict() for k, v in item.iteritems(): return_dict[self.validate_item( k, validator[0], validation_failure_info)] = (self.validate_item( v, validator[1], validation_failure_info)) item = return_dict elif '%' in validator: if type(item) is str: try: item = eval(validator.replace('%', "'" + item + "'")) except KeyError: self.validation_error(item, validation_failure_info) else: item = None elif validator == 'str': if item is not None: item = str(item) else: item = None elif validator == 'float': try: item = float(item) except (TypeError, ValueError): # TODO error pass elif validator == 'int': try: item = int(item) except (TypeError, ValueError): # TODO error pass elif validator in ('bool', 'boolean'): if type(item) is str: if item.lower() in ['false', 'f', 'no', 'disable', 'off']: item = False elif not item: item = False else: item = True elif validator == 'ms': item = Timing.string_to_ms(item) elif validator == 'secs': item = Timing.string_to_secs(item) elif validator == 'ticks': item = Timing.string_to_ticks(item) elif validator == 'ticks_int': item = int(Timing.string_to_ticks(item)) elif validator == 'list': item = Util.string_to_list(item) else: self.log.error("Invalid Validator '%s' in config spec %s:%s", validator, validation_failure_info[0][0], validation_failure_info[1]) sys.exit() return item
def __init__(self, machine, mode, name, config): self.machine = machine self.mode = mode self.name = name self.config = config self.tick_var = self.mode.name + '_' + self.name + '_tick' self.mode.player[self.tick_var] = 0 self.running = False self.start_value = 0 self.restart_on_complete = False self._ticks = 0 self.end_value = None self.ticks_remaining = 0 self.max_value = None self.direction = 'up' self.tick_secs = 1 self.timer = None self.bcp = False self.event_keys = set() self.delay = DelayManager() self.log = None self.debug = False if 'start_value' in self.config: self.start_value = self.config['start_value'] else: self.start_value = 0 if 'start_running' in self.config and self.config['start_running']: self.running = True if 'end_value' in self.config: self.end_value = self.config['end_value'] if 'control_events' in self.config and self.config['control_events']: if type(self.config['control_events']) is dict: self.config['control_events'] = [self.config['control_events']] else: self.config['control_events'] = list() if ('direction' in self.config and self.config['direction'].lower() == 'down'): self.direction = 'down' if not self.end_value: self.end_value = 0 # need it to be 0 not None if 'tick_interval' in self.config: self.tick_secs = Timing.string_to_secs(self.config[ 'tick_interval']) if 'max_value' in self.config: self.max_value = self.config['max_value'] if ('restart_on_complete' in self.config and self.config['restart_on_complete']): self.restart_on_complete = True if 'bcp' in self.config and self.config['bcp']: self.bcp = True if 'debug' in self.config and self.config['debug']: self.debug = True self.log.debug("Enabling Debug Logging") self.mode.player[self.tick_var] = self.start_value if self.log: self.log.debug("----------- Initial Values -----------") self.log.debug("running: %s", self.running) self.log.debug("start_value: %s", self.start_value) self.log.debug("restart_on_complete: %s", self.restart_on_complete) self.log.debug("_ticks: %s", self._ticks) self.log.debug("end_value: %s", self.end_value) self.log.debug("ticks_remaining: %s", self.ticks_remaining) self.log.debug("max_value: %s", self.max_value) self.log.debug("direction: %s", self.direction) self.log.debug("tick_secs: %s", self.tick_secs) self.log.debug("--------------------------------------") self._setup_control_events(self.config['control_events'])
def _initialize(self): # convert names to objects # make sure the eject timeouts list matches the length of the eject targets if (len(self.config['eject_timeouts']) < len(self.config['eject_targets'])): self.config['eject_timeouts'] += [None] * ( len(self.config['eject_targets']) - len(self.config['eject_timeouts'])) timeouts_list = self.config['eject_timeouts'] self.config['eject_timeouts'] = dict() for i in range(len(self.config['eject_targets'])): self.config['eject_timeouts'][self.config['eject_targets'][i]] = ( Timing.string_to_ms(timeouts_list[i])) # End code to create timeouts list ------------------------------------- # Register switch handlers with delays for entrance & exit counts for switch in self.config['ball_switches']: self.machine.switch_controller.add_switch_handler( switch_name=switch.name, state=1, ms=self.config['entrance_count_delay'], callback=self.count_balls) for switch in self.config['ball_switches']: self.machine.switch_controller.add_switch_handler( switch_name=switch.name, state=0, ms=self.config['exit_count_delay'], callback=self.count_balls) for switch in self.config['ball_switches']: self.machine.switch_controller.add_switch_handler( switch_name=switch.name, state=1, ms=0, callback=self._invalidate) for switch in self.config['ball_switches']: self.machine.switch_controller.add_switch_handler( switch_name=switch.name, state=0, ms=0, callback=self._invalidate) # Configure switch handlers for jam switch activity if self.config['jam_switch']: self.machine.switch_controller.add_switch_handler( switch_name=self.config['jam_switch'].name, state=1, ms=0, callback=self._jam_switch_handler) # todo do we also need to add inactive and make a smarter # handler? # Configure switch handlers for entrance switch activity if self.config['entrance_switch']: self.machine.switch_controller.add_switch_handler( switch_name=self.config['entrance_switch'].name, state=1, ms=0, callback=self._entrance_switch_handler) # todo do we also need to add inactive and make a smarter # handler? # handle hold_coil activation when a ball hits a switch for switch in self.config['hold_switches']: self.machine.switch_controller.add_switch_handler( switch_name=switch.name, state=1, ms=0, callback=self._enable_hold_coil) # Configure event handlers to watch for target device status changes for target in self.config['eject_targets']: # Target device is requesting a ball self.machine.events.add_handler('balldevice_' + target.name + '_ball_request', self.eject, target=target, get_ball=True) # Target device is now able to receive a ball self.machine.events.add_handler('balldevice_' + target.name + '_ok_to_receive', self._do_eject) # Get an initial ball count self.count_balls(stealth=True)
def __init__(self, machine, mode, name, config): self.machine = machine self.mode = mode self.name = name self.config = config self.tick_var = self.mode.name + "_" + self.name + "_tick" self.mode.player[self.tick_var] = 0 self.running = False self.start_value = 0 self.restart_on_complete = False self._ticks = 0 self.end_value = None self.ticks_remaining = 0 self.max_value = None self.direction = "up" self.tick_secs = 1 self.timer = None self.bcp = False self.event_keys = set() self.delay = DelayManager() self.log = None self.debug = False if "start_value" in self.config: self.start_value = self.config["start_value"] else: self.start_value = 0 if "start_running" in self.config and self.config["start_running"]: self.running = True if "end_value" in self.config: self.end_value = self.config["end_value"] if "control_events" in self.config and self.config["control_events"]: if type(self.config["control_events"]) is dict: self.config["control_events"] = [self.config["control_events"]] else: self.config["control_events"] = list() if "direction" in self.config and self.config["direction"].lower() == "down": self.direction = "down" if not self.end_value: self.end_value = 0 # need it to be 0 not None if "tick_interval" in self.config: self.tick_secs = Timing.string_to_secs(self.config["tick_interval"]) if "max_value" in self.config: self.max_value = self.config["max_value"] if "restart_on_complete" in self.config and self.config["restart_on_complete"]: self.restart_on_complete = True if "bcp" in self.config and self.config["bcp"]: self.bcp = True if "debug" in self.config and self.config["debug"]: self.debug = True self.log.debug("Enabling Debug Logging") self.mode.player[self.tick_var] = self.start_value if self.log: self.log.debug("----------- Initial Values -----------") self.log.debug("running: %s", self.running) self.log.debug("start_value: %s", self.start_value) self.log.debug("restart_on_complete: %s", self.restart_on_complete) self.log.debug("_ticks: %s", self._ticks) self.log.debug("end_value: %s", self.end_value) self.log.debug("ticks_remaining: %s", self.ticks_remaining) self.log.debug("max_value: %s", self.max_value) self.log.debug("direction: %s", self.direction) self.log.debug("tick_secs: %s", self.tick_secs) self.log.debug("--------------------------------------") self._setup_control_events(self.config["control_events"])
def __init__(self, options): self.options = options self.log = logging.getLogger("MediaController") self.log.info("Media Controller Version %s", version.__version__) self.log.info("Backbox Control Protocol Version %s", version.__bcp_version__) self.log.info("Config File Version %s", version.__config_version__) python_version = sys.version_info self.log.info("Python version: %s.%s.%s", python_version[0], python_version[1], python_version[2]) self.log.info("Platform: %s", sys.platform) self.log.info("Python executable location: %s", sys.executable) self.log.info("32-bit Python? %s", sys.maxsize < 2**32) self.config = dict() self.done = False # todo self.machine_path = None self.asset_managers = dict() self.num_assets_to_load = 0 self.window = None self.window_manager = None self.pygame = False self.pygame_requested = False self.registered_pygame_handlers = dict() self.pygame_allowed_events = list() self.socket_thread = None self.receive_queue = Queue.Queue() self.sending_queue = Queue.Queue() self.crash_queue = Queue.Queue() self.game_modes = CaseInsensitiveDict() self.player_list = list() self.player = None self.HZ = 0 self.next_tick_time = 0 self.secs_per_tick = 0 Task.Create(self._check_crash_queue) self.bcp_commands = {'hello': self.bcp_hello, 'goodbye': self.bcp_goodbye, 'reset': self.reset, 'mode_start': self.bcp_mode_start, 'mode_stop': self.bcp_mode_stop, 'error': self.bcp_error, 'ball_start': self.bcp_ball_start, 'ball_end': self.bcp_ball_end, 'game_start': self.bcp_game_start, 'game_end': self.bcp_game_end, 'player_added': self.bcp_player_add, 'player_variable': self.bcp_player_variable, 'player_score': self.bcp_player_score, 'player_turn_start': self.bcp_player_turn_start, 'attract_start': self.bcp_attract_start, 'attract_stop': self.bcp_attract_stop, 'trigger': self.bcp_trigger, 'switch': self.bcp_switch, 'get': self.bcp_get, 'set': self.bcp_set, 'config': self.bcp_config, 'timer': self.bcp_timer } # load the MPF config & machine defaults self.config = ( Config.load_config_yaml(config=self.config, yaml_file=self.options['mcconfigfile'])) # Find the machine_files location. If it starts with a forward or # backward slash, then we assume it's from the mpf root. Otherwise we # assume it's from the subfolder location specified in the # mpfconfigfile location if (options['machinepath'].startswith('/') or options['machinepath'].startswith('\\')): machine_path = options['machinepath'] else: machine_path = os.path.join(self.config['mediacontroller']['paths'] ['machine_files'], options['machinepath']) self.machine_path = os.path.abspath(machine_path) # Add the machine folder to our path so we can import modules from it sys.path.append(self.machine_path) self.log.info("Machine folder: %s", machine_path) # Now find the config file location. Same as machine_file with the # slash uses to specify an absolute path if (options['configfile'].startswith('/') or options['configfile'].startswith('\\')): config_file = options['configfile'] else: if not options['configfile'].endswith('.yaml'): options['configfile'] += '.yaml' config_file = os.path.join(self.machine_path, self.config['mediacontroller']['paths'] ['config'], options['configfile']) self.log.info("Base machine config file: %s", config_file) # Load the machine-specific config self.config = Config.load_config_yaml(config=self.config, yaml_file=config_file) mediacontroller_config_spec = ''' exit_on_disconnect: boolean|True port: int|5050 ''' self.config['mediacontroller'] = ( Config.process_config(mediacontroller_config_spec, self.config['mediacontroller'])) self.events = EventManager(self) self.timing = Timing(self) # Load the media controller modules self.config['mediacontroller']['modules'] = ( self.config['mediacontroller']['modules'].split(' ')) for module in self.config['mediacontroller']['modules']: self.log.info("Loading module: %s", module) module_parts = module.split('.') exec('self.' + module_parts[0] + '=' + module + '(self)') # todo there's probably a more pythonic way to do this, and I know # exec() is supposedly unsafe, but meh, if you have access to put # malicious files in the system folder then you have access to this # code too. self.start_socket_thread() self.events.post("init_phase_1") self.events.post("init_phase_2") self.events.post("init_phase_3") self.events.post("init_phase_4") self.events.post("init_phase_5") self.reset()
def _initialize(self): # convert names to objects if self.config["ball_switches"]: for i in range(len(self.config["ball_switches"])): self.config["ball_switches"][i] = self.machine.switches[self.config["ball_switches"][i]] if self.config["eject_coil"]: self.config["eject_coil"] = self.machine.coils[self.config["eject_coil"]] if self.config["eject_switch"]: self.config["eject_switch"] = self.machine.switches[self.config["eject_switch"]] if self.config["entrance_switch"]: self.config["entrance_switch"] = self.machine.switches[self.config["entrance_switch"]] if self.config["jam_switch"]: self.config["jam_switch"] = self.machine.switches[self.config["jam_switch"]] if self.config["confirm_eject_type"] == "switch" and (self.config["confirm_eject_target"]): self.config["confirm_eject_switch"] = self.machine.switches[self.config["confirm_eject_switch"]] if self.config["eject_targets"]: for i in range(len(self.config["eject_targets"])): self.config["eject_targets"][i] = self.machine.balldevices[self.config["eject_targets"][i]] # make sure the eject timeouts list matches the length of the eject targets if len(self.config["eject_timeouts"]) < len(self.config["eject_targets"]): self.config["eject_timeouts"] += [None] * ( len(self.config["eject_targets"]) - len(self.config["eject_timeouts"]) ) timeouts_list = self.config["eject_timeouts"] self.config["eject_timeouts"] = dict() for i in range(len(self.config["eject_targets"])): self.config["eject_timeouts"][self.config["eject_targets"][i]] = Timing.string_to_ms(timeouts_list[i]) # End code to create timeouts list ------------------------------------- # Register switch handlers with delays for entrance & exit counts for switch in self.config["ball_switches"]: self.machine.switch_controller.add_switch_handler( switch_name=switch.name, state=1, ms=self.config["entrance_count_delay"], callback=self.count_balls ) for switch in self.config["ball_switches"]: self.machine.switch_controller.add_switch_handler( switch_name=switch.name, state=0, ms=self.config["exit_count_delay"], callback=self.count_balls ) for switch in self.config["ball_switches"]: self.machine.switch_controller.add_switch_handler( switch_name=switch.name, state=1, ms=0, callback=self._invalidate ) for switch in self.config["ball_switches"]: self.machine.switch_controller.add_switch_handler( switch_name=switch.name, state=0, ms=0, callback=self._invalidate ) # Configure switch handlers for jam switch activity if self.config["jam_switch"]: self.machine.switch_controller.add_switch_handler( switch_name=self.config["jam_switch"].name, state=1, ms=0, callback=self._jam_switch_handler ) # todo do we also need to add inactive and make a smarter # handler? # Configure switch handlers for entrance switch activity if self.config["entrance_switch"]: self.machine.switch_controller.add_switch_handler( switch_name=self.config["entrance_switch"].name, state=1, ms=0, callback=self._entrance_switch_handler ) # todo do we also need to add inactive and make a smarter # handler? # Configure event handlers to watch for target device status changes for target in self.config["eject_targets"]: # Target device is requesting a ball self.machine.events.add_handler( "balldevice_" + target.name + "_ball_request", self.eject, target=target, get_ball=True ) # Target device is now able to receive a ball self.machine.events.add_handler("balldevice_" + target.name + "_ok_to_receive", self._do_eject) # Get an initial ball count self.count_balls(stealth=True)
def __init__(self, machine, name, config, collection=None): self.log = logging.getLogger('Diverter.' + name) super(Diverter, self).__init__(machine, name, config, collection) self.delay = DelayManager() # Attributes self.active = False self.enabled = False self.platform = None # configure defaults: if 'type' not in self.config: self.config['type'] = 'pulse' # default to pulse to not fry coils if 'activation_time' not in self.config: self.config['activation_time'] = 0 if 'activation_switches' in self.config: self.config['activation_switches'] = Config.string_to_list( self.config['activation_switches']) else: self.config['activation_switches'] = list() if 'disable_switches' in self.config: self.config['disable_switches'] = Config.string_to_list( self.config['disable_switches']) else: self.config['disable_switches'] = list() if 'deactivation_switches' in self.config: self.config['deactivation_switches'] = Config.string_to_list( self.config['deactivation_switches']) else: self.config['deactivation_switches'] = list() if 'activation_coil' in self.config: self.config['activation_coil'] = ( self.machine.coils[self.config['activation_coil']]) if 'deactivation_coil' in self.config: self.config['deactivation_coil'] = ( self.machine.coils[self.config['deactivation_coil']]) else: self.config['deactivation_coil'] = None if 'targets_when_active' in self.config: self.config['targets_when_active'] = Config.string_to_list( self.config['targets_when_active']) else: self.config['targets_when_active'] = ['playfield'] if 'targets_when_inactive' in self.config: self.config['targets_when_inactive'] = Config.string_to_list( self.config['targets_when_inactive']) else: self.config['targets_when_inactive'] = ['playfield'] if 'feeder_devices' in self.config: self.config['feeder_devices'] = Config.string_to_list( self.config['feeder_devices']) else: self.config['feeder_devices'] = list() # Create a list of ball device objects when active and inactive. We need # this because ball eject attempts pass the target device as an object # rather than by name. self.config['active_objects'] = list() self.config['inactive_objects'] = list() for target_device in self.config['targets_when_active']: if target_device == 'playfield': self.config['active_objects'].append('playfield') else: self.config['active_objects'].append( self.machine.balldevices[target_device]) for target_device in self.config['targets_when_inactive']: if target_device == 'playfield': self.config['inactive_objects'].append('playfield') else: self.config['inactive_objects'].append( self.machine.balldevices[target_device]) # convert the activation_time to ms self.config['activation_time'] = Timing.string_to_ms( self.config['activation_time']) # register for events for event in self.config['enable_events']: self.machine.events.add_handler(event, self.enable) for event in self.config['disable_events']: self.machine.events.add_handler(event, self.disable) # register for feeder device eject events for feeder_device in self.config['feeder_devices']: self.machine.events.add_handler( 'balldevice_' + feeder_device + '_ball_eject_attempt', self._feeder_eject_attempt) self.machine.events.add_handler('init_phase_3', self._register_switches) self.platform = self.config['activation_coil'].platform
def __init__(self, slide, machine, x=None, y=None, h_pos=None, v_pos=None, layer=0, **kwargs): super(CharacterPicker, self).__init__(slide, x, y, h_pos, v_pos, layer) self.fonts = machine.display.fonts self.slide = slide self.machine = machine self.layer = layer self.config = deepcopy(kwargs) self.char_list = deque() self.cursor_position = 0 self.selected_char = '' self.registered_event_handlers = list() if 'selected_char_color' not in self.config: self.config['selected_char_color'] = 0 if 'selected_char_bg' not in self.config: self.config['selected_char_bg'] = 15 if 'char_width' not in self.config: self.config['char_width'] = 11 if 'width' not in self.config: self.config['width'] = None if 'height' not in self.config: self.config['height'] = 15 if 'char_x_offset' not in self.config: self.config['char_x_offset'] = 0 if 'char_y_offset' not in self.config: self.config['char_y_offset'] = 0 if 'shift_left_tag' not in self.config: self.config['shift_left_tag'] = 'left_flipper' if 'shift_right_tag' not in self.config: self.config['shift_right_tag'] = 'right_flipper' if 'select_tag' not in self.config: self.config['select_tag'] = 'start' if 'name' in self.config: self.name = self.config['name'] else: self.name = 'character_picker' if 'char_list' not in self.config: self.config['char_list'] = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' if 'max_chars' not in self.config: self.config['max_chars'] = 3 if 'timeout' not in self.config: self.config['timeout'] = None else: self.config['timeout'] = ( Timing.string_to_secs(self.config['timeout'])) if 'back_char' not in self.config: self.config['back_char'] = 'back_arrow_7x7' if 'end_char' not in self.config: self.config['end_char'] = 'end_11x7' if 'back_char_selected' not in self.config: self.config['back_char_selected'] = 'back_arrow_7x7_selected' if 'end_char_selected' not in self.config: self.config['end_char_selected'] = 'end_11x7_selected' if 'image_padding' not in self.config: self.config['image_padding'] = 1 if 'return_param' not in self.config: self.config['return_param'] = 'award' self.config['selected_char_color'] = ( self.adjust_color(self.config['selected_char_color'])) self.config['selected_char_bg'] = ( self.adjust_color(self.config['selected_char_bg'])) self.adjust_colors(**self.config) self.config['color'] = self.adjusted_color self.config['bg_color'] = self.adjusted_bg_color self.char_list.extend(self.config['char_list']) self.char_list.append('back') self.char_list.append('end') self.char_list.rotate(len(self.char_list)/2) self.cursor_position = len(self.char_list)/2 self.selected_char = self.char_list[self.cursor_position] self.machine._set_machine_var(name=self.name + '_chars_entered', value='') self.setup_switch_handlers() self.render()
class MediaController(object): def __init__(self, options): self.options = options self.log = logging.getLogger("MediaController") self.log.debug("Command line arguments: {}".format(self.options)) self.log.info("Media Controller Version %s", version.__version__) self.log.debug("Backbox Control Protocol Version %s", version.__bcp_version__) self.log.debug("Config File Version %s", version.__config_version__) python_version = sys.version_info if python_version[0] != 2 or python_version[1] != 7: self.log.error("Incorrect Python version. MPF requires Python 2.7." "x. You have Python %s.%s.%s.", python_version[0], python_version[1], python_version[2]) sys.exit() self.log.debug("Python version: %s.%s.%s", python_version[0], python_version[1], python_version[2]) self.log.debug("Platform: %s", sys.platform) self.log.debug("Python executable location: %s", sys.executable) self.log.debug("32-bit Python? %s", sys.maxsize < 2**32) self.active_debugger = dict() self.config = dict() self.done = False # todo self.machine_path = None self.asset_managers = dict() self.window = None self.window_manager = None self.pygame = False self.pygame_requested = False self.registered_pygame_handlers = dict() self.pygame_allowed_events = list() self.socket_thread = None self.receive_queue = Queue.Queue() self.sending_queue = Queue.Queue() self.crash_queue = Queue.Queue() self.modes = CaseInsensitiveDict() self.player_list = list() self.player = None self.HZ = 0 self.next_tick_time = 0 self.secs_per_tick = 0 self.machine_vars = CaseInsensitiveDict() self.machine_var_monitor = False self.tick_num = 0 self.delay = DelayManager() self._pc_assets_to_load = 0 self._pc_total_assets = 0 self.pc_connected = False Task.create(self._check_crash_queue) self.bcp_commands = {'ball_start': self.bcp_ball_start, 'ball_end': self.bcp_ball_end, 'config': self.bcp_config, 'error': self.bcp_error, 'get': self.bcp_get, 'goodbye': self.bcp_goodbye, 'hello': self.bcp_hello, 'machine_variable': self.bcp_machine_variable, 'mode_start': self.bcp_mode_start, 'mode_stop': self.bcp_mode_stop, 'player_added': self.bcp_player_add, 'player_score': self.bcp_player_score, 'player_turn_start': self.bcp_player_turn_start, 'player_variable': self.bcp_player_variable, 'reset': self.reset, 'set': self.bcp_set, 'shot': self.bcp_shot, 'switch': self.bcp_switch, 'timer': self.bcp_timer, 'trigger': self.bcp_trigger, } FileManager.init() self.config = dict() self._load_mc_config() self._set_machine_path() self._load_machine_config() # Find the machine_files location. If it starts with a forward or # backward slash, then we assume it's from the mpf root. Otherwise we # assume it's from the subfolder location specified in the # mpfconfig file location if (options['machine_path'].startswith('/') or options['machine_path'].startswith('\\')): machine_path = options['machine_path'] else: machine_path = os.path.join(self.config['media_controller']['paths'] ['machine_files'], options['machine_path']) self.machine_path = os.path.abspath(machine_path) # Add the machine folder to our path so we can import modules from it sys.path.append(self.machine_path) self.log.info("Machine folder: %s", machine_path) mediacontroller_config_spec = ''' exit_on_disconnect: boolean|True port: int|5050 ''' self.config['media_controller'] = ( Config.process_config(mediacontroller_config_spec, self.config['media_controller'])) self.events = EventManager(self, setup_event_player=False) self.timing = Timing(self) # Load the media controller modules self.config['media_controller']['modules'] = ( self.config['media_controller']['modules'].split(' ')) self.log.info("Loading Modules...") for module in self.config['media_controller']['modules']: self.log.debug("Loading module: %s", module) module_parts = module.split('.') exec('self.' + module_parts[0] + '=' + module + '(self)') # todo there's probably a more pythonic way to do this, and I know # exec() is supposedly unsafe, but meh, if you have access to put # malicious files in the system folder then you have access to this # code too. self.start_socket_thread() self.events.post("init_phase_1") self.events._process_event_queue() self.events.post("init_phase_2") self.events._process_event_queue() self.events.post("init_phase_3") self.events._process_event_queue() self.events.post("init_phase_4") self.events._process_event_queue() self.events.post("init_phase_5") self.events._process_event_queue() self.reset() def _load_mc_config(self): self.config = Config.load_config_file(self.options['mcconfigfile']) # Find the machine_files location. If it starts with a forward or # backward slash, then we assume it's from the mpf root. Otherwise we # assume it's from the subfolder location specified in the # mpfconfigfile location def _set_machine_path(self): if (self.options['machine_path'].startswith('/') or self.options['machine_path'].startswith('\\')): machine_path = self.options['machine_path'] else: machine_path = os.path.join(self.config['media_controller']['paths'] ['machine_files'], self.options['machine_path']) self.machine_path = os.path.abspath(machine_path) self.log.debug("Machine path: {}".format(self.machine_path)) # Add the machine folder to sys.path so we can import modules from it sys.path.append(self.machine_path) def _load_machine_config(self): for num, config_file in enumerate(self.options['configfile']): if not (config_file.startswith('/') or config_file.startswith('\\')): config_file = os.path.join(self.machine_path, self.config['media_controller']['paths']['config'], config_file) self.log.info("Machine config file #%s: %s", num+1, config_file) self.config = Util.dict_merge(self.config, Config.load_config_file(config_file)) def _check_crash_queue(self): try: crash = self.crash_queue.get(block=False) except Queue.Empty: yield 1000 else: self.log.critical("MPF Shutting down due to child thread crash") self.log.critical("Crash details: %s", crash) self.done = True def reset(self, **kwargs): """Processes an incoming BCP 'reset' command.""" self.player = None self.player_list = list() self.events.add_handler('assets_to_load', self._bcp_client_asset_loader_tick) self.events.replace_handler('timer_tick', self.asset_loading_counter) self.events.post('mc_reset_phase_1') self.events._process_event_queue() self.events.post('mc_reset_phase_2') self.events._process_event_queue() self.events.post('mc_reset_phase_3') self.events._process_event_queue() def get_window(self): """ Returns a reference to the onscreen display window. This method will set up a window if one doesn't exist yet. This method exists because there are several different modules and plugins which may want to use a window, but we don't know which combinations might be used, so we centralize the creation and management of an onscreen window here. """ if not self.window: self.window_manager = window.WindowManager(self) self.window = self.window_manager.window return self.window def request_pygame(self): """Called by a module to let the system know it would like to use Pygame. We centralize the requests instead of letting each module do their own pygame.init() so we get it in one place and can get everthing initialized in the right order. Returns: True or False, depending on whether pygame is available or not. """ if pygame and not self.pygame_requested: self.events.add_handler('init_phase_3', self._pygame_init) self.pygame_requested = True return True else: return False def _pygame_init(self): # performs the actual pygame initialization if not pygame: self.log.critical("Pygame is needed but not available. Please " "install Pygame and try again.") raise Exception("Pygame is needed but not available. Please install" " Pygame and try again.") if not self.pygame: self.log.debug("Initializing Pygame, version %s", pygame.version.ver) pygame.init() self.pygame = True self.events.add_handler('timer_tick', self.get_pygame_events, priority=1000) self.events.add_handler('timer_tick', self.asset_loading_counter) self.events.post('pygame_initialized') self.events._process_event_queue() def register_pygame_handler(self, event, handler): """Registers a method to be a handler for a certain type of Pygame event. Args: event: A string of the Pygame event name you're registering this handler for. handler: A method that will be called when this Pygame event is posted. """ if event not in self.registered_pygame_handlers: self.registered_pygame_handlers[event] = set() self.registered_pygame_handlers[event].add(handler) self.pygame_allowed_events.append(event) self.log.debug("Adding Window event handler. Event:%s, Handler:%s", event, handler) pygame.event.set_allowed(self.pygame_allowed_events) def get_pygame_events(self): """Gets (and dispatches) Pygame events. Automatically called every machine loop via the timer_tick event. """ for event in pygame.event.get(): if event.type in self.registered_pygame_handlers: for handler in self.registered_pygame_handlers[event.type]: if (event.type == pygame.KEYDOWN or event.type == pygame.KEYUP): handler(event.key, event.mod) else: handler() def _process_command(self, bcp_command, **kwargs): self.log.debug("Processing command: %s %s", bcp_command, kwargs) # Can't use try/except KeyError here becasue there could be a KeyError # in the callback which we don't want it to swallow. if bcp_command in self.bcp_commands: self.bcp_commands[bcp_command](**kwargs) else: self.log.warning("Received invalid BCP command: %s", bcp_command) self.send('error', message='invalid command', command=bcp_command) def send(self, bcp_command, callback=None, **kwargs): """Sends a BCP command to the connected pinball controller. Args: bcp_command: String of the BCP command name. callback: Optional callback method that will be called when the command is sent. **kwargs: Optional additional kwargs will be added to the BCP command string. """ self.sending_queue.put(bcp.encode_command_string(bcp_command, **kwargs)) if callback: callback() def send_dmd_frame(self, data): """Sends a DMD frame to the BCP client. Args: data: A 4096-length raw byte string. """ dmd_string = 'dmd_frame?' + data self.sending_queue.put(dmd_string) def _timer_init(self): self.HZ = 30 self.next_tick_time = time.time() self.secs_per_tick = 1.0 / self.HZ def timer_tick(self): """Called by the platform each machine tick based on self.HZ""" self.timing.timer_tick() # notifies the timing module self.events.post('timer_tick') # sends the timer_tick system event self.tick_num += 1 Task.timer_tick() # notifies tasks DelayManager.timer_tick(self) self.events._process_event_queue() def run(self): """Main run loop.""" self._timer_init() self.log.info("Starting the run loop at %sHz", self.HZ) start_time = time.time() loops = 0 secs_per_tick = self.secs_per_tick self.next_tick_time = time.time() try: while self.done is False: time.sleep(0.001) self.get_from_queue() if self.next_tick_time <= time.time(): # todo change this self.timer_tick() self.next_tick_time += secs_per_tick loops += 1 self._do_shutdown() self.log.info("Target loop rate: %s Hz", self.HZ) self.log.info("Actual loop rate: %s Hz", loops / (time.time() - start_time)) except KeyboardInterrupt: self.shutdown() def shutdown(self): """Shuts down and exits the media controller. This method will also send the BCP 'goodbye' command to any connected clients. """ self.socket_thread.stop() def _do_shutdown(self): if self.pygame: pygame.quit() def socket_thread_stopped(self): """Notifies the media controller that the socket thread has stopped.""" self.done = True def start_socket_thread(self): """Starts the BCPServer socket thread.""" self.socket_thread = BCPServer(self, self.receive_queue, self.sending_queue) self.socket_thread.daemon = True self.socket_thread.start() def get_from_queue(self): """Gets and processes all queued up incoming BCP commands.""" while not self.receive_queue.empty(): cmd, kwargs = bcp.decode_command_string( self.receive_queue.get(False)) self._process_command(cmd, **kwargs) def bcp_hello(self, **kwargs): """Processes an incoming BCP 'hello' command.""" try: if LooseVersion(kwargs['version']) == ( LooseVersion(version.__bcp_version__)): self.send('hello', version=version.__bcp_version__) else: self.send('hello', version='unknown protocol version') except KeyError: self.log.warning("Received invalid 'version' parameter with " "'hello'") def bcp_goodbye(self, **kwargs): """Processes an incoming BCP 'goodbye' command.""" if self.config['media_controller']['exit_on_disconnect']: self.socket_thread.sending_thread.stop() sys.exit() def bcp_mode_start(self, name=None, priority=0, **kwargs): """Processes an incoming BCP 'mode_start' command.""" if not name: return #todo raise error if name == 'game': self._game_start() if name in self.modes: self.modes[name].start(priority=priority) def bcp_mode_stop(self, name, **kwargs): """Processes an incoming BCP 'mode_stop' command.""" if not name: return #todo raise error if name == 'game': self._game_end() if name in self.modes: self.modes[name].stop() def bcp_error(self, **kwargs): """Processes an incoming BCP 'error' command.""" self.log.warning('Received error command from client') def bcp_ball_start(self, **kwargs): """Processes an incoming BCP 'ball_start' command.""" kwargs['player'] = kwargs.pop('player_num') self.events.post('ball_started', **kwargs) def bcp_ball_end(self, **kwargs): """Processes an incoming BCP 'ball_end' command.""" self.events.post('ball_ended', **kwargs) def _game_start(self, **kargs): """Processes an incoming BCP 'game_start' command.""" self.player = None self.player_list = list() self.num_players = 0 self.events.post('game_started', **kargs) def _game_end(self, **kwargs): """Processes an incoming BCP 'game_end' command.""" self.player = None self.events.post('game_ended', **kwargs) def bcp_player_add(self, player_num, **kwargs): """Processes an incoming BCP 'player_add' command.""" if player_num > len(self.player_list): new_player = Player(self, self.player_list) self.events.post('player_add_success', num=player_num) def bcp_player_variable(self, name, value, prev_value, change, player_num, **kwargs): """Processes an incoming BCP 'player_variable' command.""" try: self.player_list[int(player_num)-1][name] = value except (IndexError, KeyError): pass def bcp_machine_variable(self, name, value, **kwargs): """Processes an incoming BCP 'machine_variable' command.""" self._set_machine_var(name, value) def bcp_player_score(self, value, prev_value, change, player_num, **kwargs): """Processes an incoming BCP 'player_score' command.""" try: self.player_list[int(player_num)-1]['score'] = int(value) except (IndexError, KeyError): pass def bcp_player_turn_start(self, player_num, **kwargs): """Processes an incoming BCP 'player_turn_start' command.""" self.log.debug("bcp_player_turn_start") if ((self.player and self.player.number != player_num) or not self.player): try: self.player = self.player_list[int(player_num)-1] except IndexError: self.log.error('Received player turn start for player %s, but ' 'only %s player(s) exist', player_num, len(self.player_list)) def bcp_trigger(self, name, **kwargs): """Processes an incoming BCP 'trigger' command.""" self.events.post(name, **kwargs) def bcp_switch(self, name, state, **kwargs): """Processes an incoming BCP 'switch' command.""" if int(state): self.events.post('switch_' + name + '_active') else: self.events.post('switch_' + name + '_inactive') def bcp_get(self, **kwargs): """Processes an incoming BCP 'get' command by posting an event 'bcp_get_<name>'. It's up to an event handler to register for that event and to send the response BCP 'set' command. """ for name in Util.string_to_list(names): self.events.post('bcp_get_{}'.format(name)) def bcp_set(self, **kwargs): """Processes an incoming BCP 'set' command by posting an event 'bcp_set_<name>' with a parameter value=<value>. It's up to an event handler to register for that event and to do something with it. Note that BCP set commands can contain multiple key/value pairs, and this method will post one event for each pair. """ for k, v in kwargs.iteritems(): self.events.post('bcp_set_{}'.format(k), value=v) def bcp_shot(self, name, profile, state): """The MPF media controller uses triggers instead of shots for its display events, so we don't need to pay attention here.""" pass def bcp_config(self, **kwargs): """Processes an incoming BCP 'config' command.""" for k, v in kwargs.iteritems(): if k.startswith('volume_'): self.bcp_set_volume(track=k.split('volume_')[1], value=v) def bcp_timer(self, name, action, **kwargs): """Processes an incoming BCP 'timer' command.""" pass def bcp_set_volume(self, track, value): """Sets the volume based on an incoming BCP 'config' command. Args: track: String name of the track the volume will set. value: Float between 0 and 1 which represents the volume level to set. Note: At this time only the master volume can be set with this method. """ if track == 'master': self.sound.set_volume(value) #if track in self.sound.tracks: #self.sound.tracks[track] # todo add per-track volume support to sound system def get_debug_status(self, debug_path): if self.options['loglevel'] > 10 or self.options['consoleloglevel'] > 10: return True class_, module = debug_path.split('|') try: if module in self.active_debugger[class_]: return True else: return False except KeyError: return False def _set_machine_var(self, name, value): try: prev_value = self.machine_vars[name] except KeyError: prev_value = None self.machine_vars[name] = value try: change = value-prev_value except TypeError: if prev_value != value: change = True else: change = False if change: 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) if self.machine_var_monitor: for callback in self.monitors['machine_var']: callback(name=name, value=self.vars[name], prev_value=prev_value, change=change) def _bcp_client_asset_loader_tick(self, total, remaining): self._pc_assets_to_load = int(remaining) self._pc_total_assets = int(total) def asset_loading_counter(self): if self.tick_num % 5 != 0: return if AssetManager.total_assets or self._pc_total_assets: # max because this could go negative at first percent = max(0, int(float(AssetManager.total_assets - self._pc_assets_to_load - AssetManager.loader_queue.qsize()) / AssetManager.total_assets * 100)) else: percent = 100 self.log.debug("Asset Loading Counter. PC remaining:{}, MC remaining:" "{}, Percent Complete: {}".format( self._pc_assets_to_load, AssetManager.loader_queue.qsize(), percent)) self.events.post('asset_loader', total=AssetManager.loader_queue.qsize() + self._pc_assets_to_load, pc=self._pc_assets_to_load, mc=AssetManager.loader_queue.qsize(), percent=percent) if not AssetManager.loader_queue.qsize(): if not self.pc_connected: self.events.post("waiting_for_client_connection") self.events.remove_handler(self.asset_loading_counter) elif not self._pc_assets_to_load: self.log.debug("Asset Loading Complete") self.events.post("asset_loading_complete") self.send('reset_complete') self.events.remove_handler(self.asset_loading_counter)
def __init__(self, machine, name, config, collection=None): self.log = logging.getLogger('Diverter.' + name) super(Diverter, self).__init__(machine, name, config, collection) self.delay = DelayManager() # Attributes self.active = False self.enabled = False # configure defaults: if 'type' not in self.config: self.config['type'] = 'pulse' # default to pulse to not fry coils if 'activation_time' not in self.config: self.config['activation_time'] = 0 if 'activation_switches' in self.config: self.config['activation_switches'] = Config.string_to_list( self.config['activation_switches']) else: self.config['activation_switches'] = list() if 'disable_switches' in self.config: self.config['disable_switches'] = Config.string_to_list( self.config['disable_switches']) else: self.config['disable_switches'] = list() if 'deactivation_switches' in self.config: self.config['deactivation_switches'] = Config.string_to_list( self.config['deactivation_switches']) else: self.config['deactivation_switches'] = list() if 'activation_coil' in self.config: self.config['activation_coil'] = ( self.machine.coils[self.config['activation_coil']]) if 'deactivation_coil' in self.config: self.config['deactivation_coil'] = ( self.machine.coils[self.config['deactivation_coil']]) else: self.config['deactivation_coil'] = None if 'targets_when_active' in self.config: self.config['targets_when_active'] = Config.string_to_list( self.config['targets_when_active']) else: self.config['targets_when_active'] = ['playfield'] if 'targets_when_inactive' in self.config: self.config['targets_when_inactive'] = Config.string_to_list( self.config['targets_when_inactive']) else: self.config['targets_when_inactive'] = ['playfield'] if 'feeder_devices' in self.config: self.config['feeder_devices'] = Config.string_to_list( self.config['feeder_devices']) else: self.config['feeder_devices'] = list() # Create a list of ball device objects when active and inactive. We need # this because ball eject attempts pass the target device as an object # rather than by name. self.config['active_objects'] = list() self.config['inactive_objects'] = list() for target_device in self.config['targets_when_active']: if target_device == 'playfield': self.config['active_objects'].append('playfield') else: self.config['active_objects'].append( self.machine.balldevices[target_device]) for target_device in self.config['targets_when_inactive']: if target_device == 'playfield': self.config['inactive_objects'].append('playfield') else: self.config['inactive_objects'].append( self.machine.balldevices[target_device]) # convert the activation_time to ms self.config['activation_time'] = Timing.string_to_ms(self.config['activation_time']) # register for events for event in self.config['enable_events']: self.machine.events.add_handler(event, self.enable) for event in self.config['disable_events']: self.machine.events.add_handler(event, self.disable) # register for feeder device eject events for feeder_device in self.config['feeder_devices']: self.machine.events.add_handler('balldevice_' + feeder_device + '_ball_eject_attempt', self._feeder_eject_attempt) # register for deactivation switches for switch in self.config['deactivation_switches']: self.machine.switch_controller.add_switch_handler( switch, self.deactivate) # register for disable switches: for switch in self.config['disable_switches']: self.machine.switch_controller.add_switch_handler( switch, self.disable)
def validate_config_item(spec, item='item not in config!@#'): try: if item.lower() == 'none': item = None except AttributeError: pass default = 'default required!@#' if '|' in spec: item_type, default = spec.split('|') if type(default) is str and default.lower() == 'none': default = None else: item_type = spec if item == 'item not in config!@#': if default == 'default required!@#': log.error( 'Required setting missing from config file. Run with ' 'verbose logging and look for the last ' 'ConfigProcessor entry above this line to see where ' 'the problem is.') sys.exit() else: item = default if item_type == 'list': return Util.string_to_list(item) if item_type == 'list_of_dicts': if type(item) is list: return item elif type(item) is dict: return [item] elif item_type == 'set': return set(Util.string_to_list(item)) elif item_type == 'dict': if type(item) is dict or type(item) is CaseInsensitiveDict: return item elif not default: return dict() else: log.error('Config error. "%s" is not a dictionary', item) sys.exit() elif item_type == 'int': try: return int(item) except TypeError: return None elif item_type == 'float': try: return float(item) except TypeError: return None elif item_type in ('string', 'str'): if item: return str(item) else: return None elif item_type in ('boolean', 'bool'): if type(item) is bool: return item else: return str(item).lower() in ('yes', 'true') elif item_type == 'ms': return Timing.string_to_ms(item) elif item_type == 'secs': return Timing.string_to_secs(item) elif item_type == 'list_of_lists': return Util.list_of_lists(item)
def __init__(self, machine, mode, name, config): self.machine = machine self.mode = mode self.name = name self.config = config self.tick_var = self.mode.name + '_' + self.name + '_tick' self.mode.player[self.tick_var] = 0 self.running = False self.start_value = 0 self.restart_on_complete = False self._ticks = 0 self.end_value = None self.ticks_remaining = 0 self.max_value = None self.direction = 'up' self.tick_secs = 1 self.timer = None self.bcp = False self.event_keys = set() self.delay = DelayManager() self.log = None self.debug = False if 'start_value' in self.config: self.start_value = self.config['start_value'] else: self.start_value = 0 if 'start_running' in self.config and self.config['start_running']: self.running = True if 'end_value' in self.config: self.end_value = self.config['end_value'] if 'control_events' in self.config and self.config['control_events']: if type(self.config['control_events']) is dict: self.config['control_events'] = [self.config['control_events']] else: self.config['control_events'] = list() if ('direction' in self.config and self.config['direction'].lower() == 'down'): self.direction = 'down' if not self.end_value: self.end_value = 0 # need it to be 0 not None if 'tick_interval' in self.config: self.tick_secs = Timing.string_to_secs(self.config['tick_interval']) if 'max_value' in self.config: self.max_value = self.config['max_value'] if ('restart_on_complete' in self.config and self.config['restart_on_complete']): self.restart_on_complete = True if 'bcp' in self.config and self.config['bcp']: self.bcp = True if 'debug' in self.config and self.config['debug']: self.debug = True self.log.debug("Enabling Debug Logging") self.mode.player[self.tick_var] = self.start_value if self.log: self.log.debug("----------- Initial Values -----------") self.log.debug("running: %s", self.running) self.log.debug("start_value: %s", self.start_value) self.log.debug("restart_on_complete: %s", self.restart_on_complete) self.log.debug("_ticks: %s", self._ticks) self.log.debug("end_value: %s", self.end_value) self.log.debug("ticks_remaining: %s", self.ticks_remaining) self.log.debug("max_value: %s", self.max_value) self.log.debug("direction: %s", self.direction) self.log.debug("tick_secs: %s", self.tick_secs) self.log.debug("--------------------------------------") self._setup_control_events(self.config['control_events'])
def preprocess_settings(self, settings, base_priority=0): """Takes an unstructured list of SlidePlayer settings and processed them so they can be displayed. Args: settings: A list of dictionary of SlidePlayer settings for a slide. base_priority: An integer that will be added to slide's priority from the config settings. Returns: A python list with all the settings in the right places. This method does a bunch of things, like making sure all the needed values are there, and moving certain things to the first and last elements when there are multiple elements used on one slide. (For example, if one of the elements wants to clear the slide, it has to happen first. If there's a transition, it has to happen last after the slide is built, etc. The returned settings list can be safely called with the by display() with the preprocessed=True flag. """ # This is a stupid band-aid because when modes load their slideplayer # settings are already processed. I don't know why though, but I don't # have time to track it down now. $50 to anyone who figures out why!!! # Settings can be a list of dicts or just a dict. (Preprocessing is what # turns a dict into a list, though I don't know how sometimes items are # getting the preprocessed entry in their dict but they're not a list??? # todo if type(settings) is list and 'preprocessed' in settings[0]: return settings elif type(settings) is dict and 'preprocessed' in settings: return [settings] processed_settings = list() if type(settings) is dict: settings = [settings] last_settings = dict() first_settings = dict() # Drop this key into the settings so we know they've been preprocessed. first_settings['preprocessed'] = True for element in settings: # Create a slide name based on the event name if one isn't specified if 'slide_name' in element: first_settings['slide_name'] = element.pop('slide_name') if 'removal_key' in element: first_settings['removal_key'] = element.pop('removal_key') # If the config doesn't specify whether this slide should be made # active when this event is called, set a default value of True if 'slide_priority' in element: first_settings['slide_priority'] = ( element.pop('slide_priority') + base_priority) # If a 'clear_slide' setting isn't specified, set a default of True if 'clear_slide' in element: first_settings['clear_slide'] = element.pop('clear_slide') # If a 'persist_slide' setting isn't specified, set default of False if 'persist_slide' in element: first_settings['persist_slide'] = element.pop('persist_slide') if 'display' in element: first_settings['display'] = element.pop('display') if 'transition' in element: last_settings['transition'] = element.pop('transition') if 'name' not in element: element['name'] = None if 'expire' in element: first_settings['expire'] = Timing.string_to_ms( element.pop('expire')) else: first_settings['expire'] = 0 processed_settings.append(element) if 'slide_priority' not in first_settings: first_settings['slide_priority'] = base_priority if 'removal_key' not in first_settings: first_settings['removal_key'] = None # Now add back in the items that need to be in the first element processed_settings[0].update(first_settings) # And add the settings we need to the last entry processed_settings[-1].update(last_settings) return processed_settings
def preprocess_settings(self, settings, base_priority=0): """Takes an unstructured list of slide_player settings and processed them so they can be displayed. Args: settings: A list of dictionary of slide_player settings for a slide. base_priority: An integer that will be added to slide's priority from the config settings. Returns: A python list with all the settings in the right places. This method does a bunch of things, like making sure all the needed values are there, and moving certain things to the first and last elements when there are multiple elements used on one slide. (For example, if one of the elements wants to clear the slide, it has to happen first. If there's a transition, it has to happen last after the slide is built, etc. The returned settings list can be safely called with the by display() with the preprocessed=True flag. """ # This is a stupid band-aid because when modes load their slide_player # settings are already processed. I don't know why though, but I don't # have time to track it down now. $50 to anyone who figures out why!!! # Settings can be a list of dicts or just a dict. (Preprocessing is what # turns a dict into a list, though I don't know how sometimes items are # getting the preprocessed entry in their dict but they're not a list??? # todo if not settings: settings = list() settings.append(dict()) else: settings = deepcopy(settings) if type(settings) is list and 'preprocessed' in settings[0]: return settings elif type(settings) is dict and 'preprocessed' in settings: return [settings] processed_settings = list() if type(settings) is dict: settings = [settings] last_settings = dict() first_settings = dict() first_settings['preprocessed'] = True first_settings['persist_slide'] = False first_settings['clear_slide'] = False first_settings['expire'] = 0 first_settings['slide_name'] = None for element in settings: # Create a slide name based on the event name if one isn't specified if 'slide_name' in element: first_settings['slide_name'] = element.pop('slide_name') # If the config doesn't specify whether this slide should be made # active when this event is called, set a default value of True if 'slide_priority' in element: first_settings['slide_priority'] = ( element.pop('slide_priority') + base_priority) if 'clear_slide' in element: first_settings['clear_slide'] = element.pop('clear_slide') if 'slide' in element: first_settings['slide_name'] = element.pop('slide') if 'persist_slide' in element: first_settings['persist_slide'] = element.pop('persist_slide') if 'display' in element: first_settings['display'] = element.pop('display') if 'transition' in element: last_settings['transition'] = element.pop('transition') if 'expire' in element: first_settings['expire'] = Timing.string_to_ms( element.pop('expire')) processed_settings.append(element) if 'slide_priority' not in first_settings: first_settings['slide_priority'] = base_priority # Now add back in the items that need to be in the first element processed_settings[0].update(first_settings) # And add the settings we need to the last entry processed_settings[-1].update(last_settings) return processed_settings
def validate_config_item(spec, item='item not in config!@#'): try: if item.lower() == 'none': item = None except AttributeError: pass default = 'default required!@#' if '|' in spec: item_type, default = spec.split('|') if type(default) is str and default.lower() == 'none': default = None else: item_type = spec if item == 'item not in config!@#': if default == 'default required!@#': log.error('Required setting missing from config file. Run with ' 'verbose logging and look for the last ' 'ConfigProcessor entry above this line to see where ' 'the problem is.') sys.exit() else: item = default if item_type == 'list': return Util.string_to_list(item) if item_type == 'list_of_dicts': if type(item) is list: return item elif type(item) is dict: return [item] elif item_type == 'set': return set(Util.string_to_list(item)) elif item_type == 'dict': if type(item) is dict or type(item) is CaseInsensitiveDict: return item elif not default: return dict() else: log.error('Config error. "%s" is not a dictionary', item) sys.exit() elif item_type == 'int': try: return int(item) except TypeError: return None elif item_type == 'float': try: return float(item) except TypeError: return None elif item_type in ('string', 'str'): if item: return str(item) else: return None elif item_type in ('boolean', 'bool'): if type(item) is bool: return item else: return str(item).lower() in ('yes', 'true') elif item_type == 'ms': return Timing.string_to_ms(item) elif item_type == 'secs': return Timing.string_to_secs(item) elif item_type == 'list_of_lists': return Util.list_of_lists(item)
def _initialize(self): # convert names to objects if self.config['ball_switches']: for i in range(len(self.config['ball_switches'])): self.config['ball_switches'][i] = ( self.machine.switches[self.config['ball_switches'][i]]) if self.config['eject_coil']: self.config['eject_coil'] = ( self.machine.coils[self.config['eject_coil']]) if self.config['eject_switch']: self.config['eject_switch'] = ( self.machine.switches[self.config['eject_switch']]) if self.config['entrance_switch']: self.config['entrance_switch'] = ( self.machine.switches[self.config['entrance_switch']]) if self.config['jam_switch']: self.config['jam_switch'] = ( self.machine.switches[self.config['jam_switch']]) if self.config['confirm_eject_type'] == 'switch' and ( self.config['confirm_eject_target']): self.config['confirm_eject_switch'] = ( self.machine.switches[self.config['confirm_eject_switch']]) if self.config['eject_targets']: for i in range(len(self.config['eject_targets'])): self.config['eject_targets'][i] = ( self.machine.balldevices[self.config['eject_targets'][i]]) # make sure the eject timeouts list matches the length of the eject targets if (len(self.config['eject_timeouts']) < len( self.config['eject_targets'])): self.config['eject_timeouts'] += [None] * ( len(self.config['eject_targets']) - len(self.config['eject_timeouts'])) timeouts_list = self.config['eject_timeouts'] self.config['eject_timeouts'] = dict() for i in range(len(self.config['eject_targets'])): self.config['eject_timeouts'][self.config['eject_targets'][i]] = ( Timing.string_to_ms(timeouts_list[i])) # End code to create timeouts list ------------------------------------- # Register switch handlers with delays for entrance & exit counts for switch in self.config['ball_switches']: self.machine.switch_controller.add_switch_handler( switch_name=switch.name, state=1, ms=self.config['entrance_count_delay'], callback=self.count_balls) for switch in self.config['ball_switches']: self.machine.switch_controller.add_switch_handler( switch_name=switch.name, state=0, ms=self.config['exit_count_delay'], callback=self.count_balls) for switch in self.config['ball_switches']: self.machine.switch_controller.add_switch_handler( switch_name=switch.name, state=1, ms=0, callback=self._invalidate) for switch in self.config['ball_switches']: self.machine.switch_controller.add_switch_handler( switch_name=switch.name, state=0, ms=0, callback=self._invalidate) # Configure switch handlers for jam switch activity if self.config['jam_switch']: self.machine.switch_controller.add_switch_handler( switch_name=self.config['jam_switch'].name, state=1, ms=0, callback=self._jam_switch_handler) # todo do we also need to add inactive and make a smarter # handler? # Configure switch handlers for entrance switch activity if self.config['entrance_switch']: self.machine.switch_controller.add_switch_handler( switch_name=self.config['entrance_switch'].name, state=1, ms=0, callback=self._entrance_switch_handler) # todo do we also need to add inactive and make a smarter # handler? # Configure event handlers to watch for target device status changes for target in self.config['eject_targets']: # Target device is requesting a ball self.machine.events.add_handler('balldevice_' + target.name + '_ball_request', self.eject, target=target, get_ball=True) # Target device is now able to receive a ball self.machine.events.add_handler( 'balldevice_' + target.name + '_ok_to_receive', self._do_eject) # Get an initial ball count self.count_balls(stealth=True)
def __init__(self, options): self.options = options self.log = logging.getLogger("MediaController") self.log.debug("Command line arguments: {}".format(self.options)) self.log.info("Media Controller Version %s", version.__version__) self.log.debug("Backbox Control Protocol Version %s", version.__bcp_version__) self.log.debug("Config File Version %s", version.__config_version__) python_version = sys.version_info if python_version[0] != 2 or python_version[1] != 7: self.log.error("Incorrect Python version. MPF requires Python 2.7." "x. You have Python %s.%s.%s.", python_version[0], python_version[1], python_version[2]) sys.exit() self.log.debug("Python version: %s.%s.%s", python_version[0], python_version[1], python_version[2]) self.log.debug("Platform: %s", sys.platform) self.log.debug("Python executable location: %s", sys.executable) self.log.debug("32-bit Python? %s", sys.maxsize < 2**32) self.active_debugger = dict() self.config = dict() self.done = False # todo self.machine_path = None self.asset_managers = dict() self.window = None self.window_manager = None self.pygame = False self.pygame_requested = False self.registered_pygame_handlers = dict() self.pygame_allowed_events = list() self.socket_thread = None self.receive_queue = Queue.Queue() self.sending_queue = Queue.Queue() self.crash_queue = Queue.Queue() self.modes = CaseInsensitiveDict() self.player_list = list() self.player = None self.HZ = 0 self.next_tick_time = 0 self.secs_per_tick = 0 self.machine_vars = CaseInsensitiveDict() self.machine_var_monitor = False self.tick_num = 0 self.delay = DelayManager() self._pc_assets_to_load = 0 self._pc_total_assets = 0 self.pc_connected = False Task.create(self._check_crash_queue) self.bcp_commands = {'ball_start': self.bcp_ball_start, 'ball_end': self.bcp_ball_end, 'config': self.bcp_config, 'error': self.bcp_error, 'get': self.bcp_get, 'goodbye': self.bcp_goodbye, 'hello': self.bcp_hello, 'machine_variable': self.bcp_machine_variable, 'mode_start': self.bcp_mode_start, 'mode_stop': self.bcp_mode_stop, 'player_added': self.bcp_player_add, 'player_score': self.bcp_player_score, 'player_turn_start': self.bcp_player_turn_start, 'player_variable': self.bcp_player_variable, 'reset': self.reset, 'set': self.bcp_set, 'shot': self.bcp_shot, 'switch': self.bcp_switch, 'timer': self.bcp_timer, 'trigger': self.bcp_trigger, } FileManager.init() self.config = dict() self._load_mc_config() self._set_machine_path() self._load_machine_config() # Find the machine_files location. If it starts with a forward or # backward slash, then we assume it's from the mpf root. Otherwise we # assume it's from the subfolder location specified in the # mpfconfig file location if (options['machine_path'].startswith('/') or options['machine_path'].startswith('\\')): machine_path = options['machine_path'] else: machine_path = os.path.join(self.config['media_controller']['paths'] ['machine_files'], options['machine_path']) self.machine_path = os.path.abspath(machine_path) # Add the machine folder to our path so we can import modules from it sys.path.append(self.machine_path) self.log.info("Machine folder: %s", machine_path) mediacontroller_config_spec = ''' exit_on_disconnect: boolean|True port: int|5050 ''' self.config['media_controller'] = ( Config.process_config(mediacontroller_config_spec, self.config['media_controller'])) self.events = EventManager(self, setup_event_player=False) self.timing = Timing(self) # Load the media controller modules self.config['media_controller']['modules'] = ( self.config['media_controller']['modules'].split(' ')) self.log.info("Loading Modules...") for module in self.config['media_controller']['modules']: self.log.debug("Loading module: %s", module) module_parts = module.split('.') exec('self.' + module_parts[0] + '=' + module + '(self)') # todo there's probably a more pythonic way to do this, and I know # exec() is supposedly unsafe, but meh, if you have access to put # malicious files in the system folder then you have access to this # code too. self.start_socket_thread() self.events.post("init_phase_1") self.events._process_event_queue() self.events.post("init_phase_2") self.events._process_event_queue() self.events.post("init_phase_3") self.events._process_event_queue() self.events.post("init_phase_4") self.events._process_event_queue() self.events.post("init_phase_5") self.events._process_event_queue() self.reset()