def _check_sections(self, config, config_type, filename, ignore_unknown_sections): config_spec = self.config_validator.get_config_spec() if not isinstance(config, dict): raise ConfigFileError("Config should be a dict: {}".format(config), 1, self.log.name, filename) for k in config.keys(): try: if config_type not in config_spec[k]['__valid_in__']: raise ConfigFileError( 'Found a "{}:" section in config file {}, ' 'but that section is not valid in {} config ' 'files (only in {}).'.format( k, filename, config_type, config_spec[k]['__valid_in__']), 2, self.log.name, filename) except KeyError: if not ignore_unknown_sections: suggestion = self._find_similar_key( k, config_spec, config_type) raise ConfigFileError( 'Found a "{}:" section in config file {}, ' 'but that section name is unknown. Did you mean "{}:" ' 'instead?.'.format(k, filename, suggestion), 3, self.log.name, filename)
def _validate_config(self): if not self.ball_device.config['eject_coil']: raise ConfigFileError("Pulse Coil Ejector needs an eject_coil.", 1, self.ball_device.log.name + "-pulse_ejector") # if self.config['eject_coil_enable_time']: if self.ball_device.config['eject_coil_enable_time']: raise ConfigFileError( "Pulse Coil Ejector does not support eject_coil_enable_time.", 2, self.ball_device.log.name + "-pulse_ejector")
def check_for_invalid_sections(self, spec, config, validation_failure_info): """Check if all attributes are defined in spec.""" # TODO: refactor this method try: for k in config: if not isinstance(k, dict): if k not in spec and k[0] != '_': path_list = validation_failure_info.parent.item.split( ':') if len(path_list) > 1 and path_list[ -1] == validation_failure_info[1]: path_list.append('[list_item]') elif path_list[0] == validation_failure_info[1]: path_list = list() path_list.append(validation_failure_info[1]) path_list.append(k) path_string = ':'.join(path_list) if "mpf" in self.machine.config and self.machine.config[ 'mpf']['allow_invalid_config_sections']: self.log.warning( 'Unrecognized config setting. "%s" is ' 'not a valid setting name.', path_string) else: self.log.error( 'Your config contains a value for the ' 'setting "%s", but this is not a valid ' 'setting name.', path_string) raise ConfigFileError( 'Your config contains a value for the ' 'setting "' + path_string + '", but this is not a valid ' 'setting name.', 2, self.log.name) except TypeError: raise ConfigFileError( 'Error in config. Your "{}:" section contains a value that is ' 'not a parent with sub-settings: {}'.format( validation_failure_info.parent.item, config), 3, self.log.name)
def get_hw_switch_states(self): """Return hw switch states.""" if not self.initial_states_sent: if 'virtual_platform_start_active_switches' in self.machine.config: initial_active_switches = [] for switch in Util.string_to_list( self.machine. config['virtual_platform_start_active_switches']): if switch not in self.machine.switches: raise ConfigFileError( "Switch {} used in virtual_platform_start_active_switches was not found " "in switches section.".format(switch), 1, self.log.name) initial_active_switches.append( self.machine.switches[switch].hw_switch.number) for k in self.hw_switches: if k in initial_active_switches: self.hw_switches[k] ^= 1 self.initial_states_sent = True else: switches = [x for x in self.machine.switches if x.platform == self] for switch in switches: self.hw_switches[ switch.hw_switch.number] = switch.state ^ switch.invert return self.hw_switches
def run(self): """Run loop for the loader thread.""" try: # wrap the so we can send exceptions to the main thread while not self.thread_stopper.is_set(): try: asset = self.loader_queue.get(block=True, timeout=1) except Empty: asset = None if asset: with asset.lock: if not asset.loaded: try: asset.do_load() except Exception as e: raise ConfigFileError( "Error while loading {} asset file '{}'".format(asset.attribute, asset.file), 1, self.log.name, asset.name) from e self.loaded_queue.put((asset, True)) else: self.loaded_queue.put((asset, False)) return # pylint: disable-msg=broad-except except Exception: # pragma: no cover exc_type, exc_value, exc_traceback = sys.exc_info() lines = traceback.format_exception(exc_type, exc_value, exc_traceback) msg = ''.join(line for line in lines) self.exception_queue.put(msg) raise
def validate_config_entry(self, settings, name): """Validate one entry of this player.""" validated_config = dict() # settings here is the same as a show entry, so we process with # that if not isinstance(settings, dict): if isinstance(settings, (str, int, float)): settings = self.get_string_config(settings) else: raise AssertionError( "Invalid settings for player {}:{} {}".format( name, self.show_section, settings)) # settings here are dicts of devices/settings for device, device_settings in settings.items(): try: validated_config.update( self._validate_config_item(device, device_settings)) except ConfigFileError as e: raise ConfigFileError( "Failed to load config player {}:{} {}".format( name, self.show_section, settings), 1, self.log.name) from e return validated_config
def raise_config_error(self, msg, error_no, *, source_exception=None, context=None) -> NoReturn: """Raise a ConfigFileError exception.""" raise ConfigFileError(msg, error_no, self.log.name if self.log else "", context, self._url_base) \ from source_exception
def __init__(self, number, config, machine): """Initialise stepper.""" self.config = config self.log = logging.getLogger('TIC Stepper') self.log.debug("Configuring Stepper Parameters.") self.serial_number = number self.tic = PololuTiccmdWrapper(self.serial_number, machine, False) self.machine = machine self._position = None self._watchdog_task = None if self.config['step_mode'] not in [1, 2, 4, 8, 16, 32]: raise ConfigFileError("step_mode must be one of (1, 2, 4, 8, 16, or 32)", 1, self.log.name) if self.config['max_speed'] <= 0: raise ConfigFileError("max_speed must be greater than 0", 2, self.log.name) if self.config['max_speed'] > 500000000: raise ConfigFileError("max_speed must be less than or equal to 500,000,000", 3, self.log.name)
def validation_error(self, item, validation_failure_info, msg="", code=None): """Raise a validation error with all relevant infos.""" raise ConfigFileError( "Config validation error: Entry {} = \"{}\" is not valid. {}". format(self._build_error_path(validation_failure_info), item, msg), 5 if code is None else code, self.log.name)
def validate_config_item(self, spec, validation_failure_info, item='item not in config!@#', ): """Validate a config item.""" try: item_type, validation, default = spec except (ValueError, AttributeError): raise ValueError('Error in validator spec: {}:{}'.format( validation_failure_info, spec)) if default.lower() == 'none': default = None elif not default: default = 'default required!@#' if item == 'item not in config!@#': if default == 'default required!@#': self.validation_error("None", validation_failure_info, 'Required setting {} missing from config file.'.format( validation_failure_info[1]), 9) else: item = default if item_type == 'single': return self.validate_item(item, validation, validation_failure_info) if item_type == 'list': item_list = Util.string_to_list(item) new_list = list() for i in item_list: new_list.append(self.validate_item(i, validation, validation_failure_info)) return new_list if item_type == 'set': item_set = set(Util.string_to_list(item)) new_set = set() for i in item_set: new_set.add(self.validate_item(i, validation, validation_failure_info)) return new_set if item_type == "event_handler": if validation != "str:ms": raise AssertionError("event_handler should use str:ms in config_spec: {}".format(spec)) return self._validate_dict_or_omap(item_type, validation, validation_failure_info, item) if item_type in ('dict', 'omap'): return self._validate_dict_or_omap(item_type, validation, validation_failure_info, item) raise ConfigFileError("Invalid Type '{}' in config spec {}:{}".format(item_type, validation_failure_info[0][0], validation_failure_info[1]), 1, self.log.name)
def validate_and_parse_config(self, config: dict, is_mode_config: bool, debug_prefix: str = None): """Validate and parse spinner config.""" config = super().validate_and_parse_config(config, is_mode_config, debug_prefix) for switch in config['switch']: if switch not in config['switches']: config['switches'].append(switch) if config['labels'] and len(config['labels']) != len(config['switches']): raise ConfigFileError("Spinner labels must be the same number as switches", 1, self.name) return config
async def configure_servo(self, number: str): """Configure servo.""" try: i2c_address, servo_number = number.rsplit("-", 1) except ValueError: servo_number = number i2c_address = 0x40 try: number_int = int(servo_number) except ValueError: raise ConfigFileError("Invalid servo number {} in {}.".format(servo_number, number), 2, self.log.name) i2c_device = await self._initialize_controller(i2c_address) # check bounds if number_int < 0 or number_int > 15: raise ConfigFileError("Invalid number {} in {}. The controller only supports servos 0 to 15.".format( number_int, number), 1, self.log.name) return I2cServo(number_int, self.config, i2c_device)
def __init__(self, number, config, platform): """Initialise stepper.""" self.config = config self.log = logging.getLogger('TIC Stepper') self.log.debug("Configuring Stepper Parameters.") self.serial_number = number self.tic = PololuTiccmdWrapper(self.serial_number, platform.machine, False) self.machine = platform.machine # type: MachineController self.platform = platform self._position = None self._watchdog_task = None self._poll_task = None self._move_complete = asyncio.Event() self._switch_state = {} if self.config['step_mode'] not in [1, 2, 4, 8, 16, 32]: raise ConfigFileError("step_mode must be one of (1, 2, 4, 8, 16, or 32)", 1, self.log.name) if self.config['max_speed'] <= 0: raise ConfigFileError("max_speed must be greater than 0", 2, self.log.name) if self.config['max_speed'] > 500000000: raise ConfigFileError("max_speed must be less than or equal to 500,000,000", 3, self.log.name)
def __init__(self, config, ball_device, machine): """Initialise pulse coil ejector.""" for option in ["eject_coil", "eject_coil_jam_pulse", "eject_coil_retry_pulse", "eject_coil_reorder_pulse", "eject_coil_max_wait_ms", "retries_before_increasing_pulse"]: if option not in config and option in ball_device.config: config[option] = ball_device.config[option] super().__init__(config, ball_device, machine) self.config = self.machine.config_validator.validate_config("ball_device_ejector_pulse", self.config) # prevent conflicting options if "eject_coil_enable_time" in self.ball_device.config and self.ball_device.config['eject_coil_enable_time']: raise ConfigFileError("Pulse Coil Ejector does not support eject_coil_enable_time.", 2, self.ball_device.log.name + "-pulse_ejector")
def _check_duplicate_light_numbers(machine: MachineController, **kwargs): del kwargs check_set = set() for light in machine.lights.values(): for drivers in light.hw_drivers.values(): for driver in drivers: key = (light.config['platform'], driver.number, type(driver)) if key in check_set: raise ConfigFileError( "Duplicate light number {} {} for light {}".format( type(driver), driver.number, light), 10, "light", key, "light") check_set.add(key)
def register_player_events(self, config, mode: Mode = None, priority=0): """Register events for standalone player.""" # config is localized key_list = list() subscription_list = dict() # type: Dict[BoolTemplate, asyncio.Future] if config: for event, settings in config.items(): # prevent runtime crashes if (not mode or (mode and not mode.is_game_mode) ) and not self.is_entry_valid_outside_mode(settings): raise ConfigFileError( "Section not valid outside of game modes. {} {}:{} Mode: {}" .format(self, event, settings, mode), 1, self.config_file_section) if event.startswith("{") and event.endswith("}"): condition = event[1:-1] self._create_subscription(condition, subscription_list, settings, priority, mode) else: event, actual_priority = self._parse_event_priority( event, priority) if mode and event in mode.config['mode']['start_events']: self.machine.log.error( "{0} mode's {1}: section contains a \"{2}:\" event " "which is also in the start_events: for the {0} mode. " "Change the {1}: {2}: event name to " "\"mode_{0}_started:\"".format( mode.name, self.config_file_section, event)) raise ValueError( "{0} mode's {1}: section contains a \"{2}:\" event " "which is also in the start_events: for the {0} mode. " "Change the {1}: {2}: event name to " "\"mode_{0}_started:\"".format( mode.name, self.config_file_section, event)) key_list.append( self.machine.events.add_handler( event=event, handler=self.config_play_callback, calling_context=event, priority=actual_priority, mode=mode, settings=settings)) return key_list, subscription_list
def __init__(self, machine): """Initialise RGB DMD.""" super().__init__(machine) self.features['tickless'] = True self.log = logging.getLogger('SmartMatrix') self.log.debug("Configuring SmartMatrix RGB DMD hardware interface.") self.devices = dict() # type: Dict[str, SmartMatrixDevice] if not isinstance(self.machine.config['smartmatrix'], dict): raise ConfigFileError("Smartmatrix config needs to be a dict.", 1, self.log.name) for name, config in self.machine.config['smartmatrix'].items(): config = self.machine.config_validator.validate_config( config_spec='smartmatrix', source=config) self.devices[name] = SmartMatrixDevice(config, machine)
def validate_item(self, item, validator, validation_failure_info): """Validate an item using a validator.""" try: if item.lower() == 'none': item = None except AttributeError: pass if '(' in validator and validator[-1:] == ')': validator_parts = validator.split('(', maxsplit=1) validator = validator_parts[0] param = validator_parts[1][:-1] return self.validator_list[validator](item, validation_failure_info=validation_failure_info, param=param) if validator in self.validator_list: return self.validator_list[validator](item, validation_failure_info=validation_failure_info) raise ConfigFileError("Invalid Validator '{}' in config spec {}:{}".format( validator, validation_failure_info[0][0], validation_failure_info[1]), 4, self.log.name)
def _show_validation_error(self, msg, error_code) -> "NoReturn": # pragma: no cover raise ConfigFileError('"{}" >> {}'.format(self.name, msg), error_code, "show", self.name)
def raise_config_error(self, msg, error_no, *, context=None): """Raise a ConfigFileError exception.""" raise ConfigFileError(msg, error_no, self.log.name, context, self._url_base)
async def _initialize(self): await super()._initialize() self.platform = self.machine.get_platform_sections( 'switches', self.config['platform']) if self.config['type'].upper() == 'NC': self.invert = 1 self.recycle_secs = self.config['ignore_window_ms'] / 1000.0 config = SwitchConfig(name=self.name, invert=self.invert, debounce=self.config['debounce']) if not self.platform.features[ 'allow_empty_numbers'] and self.config['number'] is None: self.raise_config_error("Switch must have a number.", 1) try: self.hw_switch = self.platform.configure_switch( self.config['number'], config, self.config['platform_settings']) except AssertionError as e: raise ConfigFileError( "Failed to configure switch {} in platform. See error above". format(self.name), 2, self.class_label) from e if self.recycle_secs: self.add_handler(state=1, callback=self._post_events_with_recycle, callback_kwargs={"state": 1}) self.add_handler(state=0, callback=self._post_events_with_recycle, callback_kwargs={"state": 0}) else: self.add_handler(state=1, callback=self._post_events, callback_kwargs={"state": 1}) self.add_handler(state=0, callback=self._post_events, callback_kwargs={"state": 0}) if self.machine.config['mpf']['auto_create_switch_events']: self._create_activation_event( self.machine.config['mpf']['switch_event_active'].replace( '%', self.name), 1) '''event: (name)_active desc: Posted when this switch becomes active. Note that this will only be posted if there is an event handler for it or if debug is set to True on this switch for performance reasons. ''' self._create_activation_event( self.machine.config['mpf']['switch_event_inactive'].replace( '%', self.name), 0) '''event: (name)_inactive desc: Posted when this switch becomes inactive. Note that this will only be posted if there is an event handler for it or if debug is set to True on this switch for performance reasons. ''' for tag in self.tags: self._create_activation_event( self.machine.config['mpf']['switch_tag_event'].replace( '%', tag), 1) '''event: sw_(tag) desc: Posted when a switch with this tag becomes active. Note that this will only be posted if there is an event handler for it or if debug is set to True on this switch for performance reasons. ''' self._create_activation_event( self.machine.config['mpf']['switch_tag_event'].replace( '%', tag) + "_active", 1) '''event: sw_(tag)_active desc: Posted when a switch with this tag becomes active. Note that this will only be posted if there is an event handler for it or if debug is set to True on this switch for performance reasons. ''' self._create_activation_event( self.machine.config['mpf']['switch_tag_event'].replace( '%', tag) + "_inactive", 0) '''event: sw_(tag)_inactive desc: Posted when a switch with this tag becomes inactive. Note that this will only be posted if there is an event handler for it or if debug is set to True on this switch for performance reasons. ''' for event in Util.string_to_event_list( self.config['events_when_activated']): self._create_activation_event(event, 1) for event in Util.string_to_event_list( self.config['events_when_deactivated']): self._create_activation_event(event, 0)
def validate_config_item( self, spec, validation_failure_info, item='item not in config!@#', ): """Validate a config item.""" try: item_type, validation, default = spec except (ValueError, AttributeError): raise ValueError('Error in validator spec: {}:{}'.format( validation_failure_info, spec)) if default.lower() == 'none': default = None elif not default: default = 'default required!@#' if item == 'item not in config!@#': if default == 'default required!@#': section = self._build_error_path( validation_failure_info.parent) self.validation_error( "None", validation_failure_info, 'Required setting "{}:" is missing from section "{}:" in your config.' .format(validation_failure_info.item, section), 9) else: item = default if item_type == 'single': return self.validate_item(item, validation, validation_failure_info) if item_type == 'list': if validation in ("event_posted", "event_handler"): item_list = Util.string_to_list(item) else: item_list = Util.string_to_event_list(item) new_list = list() for i in item_list: if i in ("", " "): self.validation_error(item, validation_failure_info, "List contains an empty element.", 15) new_list.append( self.validate_item(i, validation, validation_failure_info)) return new_list if item_type == 'set': item_set = set(Util.string_to_list(item)) new_set = set() for i in item_set: new_set.add( self.validate_item(i, validation, validation_failure_info)) return new_set if item_type == "event_handler": if validation != "event_handler:ms": raise AssertionError( "event_handler should use event_handler:ms in config_spec: {}" .format(spec)) return self._validate_dict_or_omap(item_type, validation, validation_failure_info, item) if item_type in ('dict', 'omap'): return self._validate_dict_or_omap(item_type, validation, validation_failure_info, item) raise ConfigFileError( "Invalid Type '{}' in config spec {}".format( item_type, self._build_error_path(validation_failure_info)), 1, self.log.name)