diff --git a/mpf/core/bcp/bcp_interface.py b/mpf/core/bcp/bcp_interface.py index 52498084b..ae940c55a 100644 --- a/mpf/core/bcp/bcp_interface.py +++ b/mpf/core/bcp/bcp_interface.py @@ -101,10 +101,10 @@ def remove_registered_trigger_event_for_client(self, client, event): if not self.machine.bcp.transport.get_transports_for_handler(event): self.machine.events.remove_handler_by_event(event=event, handler=self.bcp_trigger) - async def _bcp_receive_set_machine_var(self, client, name, value): + async def _bcp_receive_set_machine_var(self, client, name, value, persist=False): """Set machine var via bcp.""" del client - self.machine.variables.set_machine_var(name, value) + self.machine.variables.set_machine_var(name, value, persist) # document variables injected by MC '''machine_var: mc_version @@ -458,8 +458,8 @@ def _monitor_machine_vars(self, client): self.machine.register_monitor('machine_vars', self._machine_var_change) # Send initial machine variable values - for s in ("standard", "feature", "game", "coin"): - self._send_machine_vars(client, setting_type=s) + self._send_machine_vars(client) + self._send_machine_settings(client) # Establish handler for machine variable changes self.machine.bcp.transport.add_handler_to_transport("_machine_vars", client) @@ -471,10 +471,14 @@ def _monitor_machine_vars_stop(self, client): if not self.machine.bcp.transport.get_transports_for_handler("_machine_vars"): self.machine.machine_var_monitor = False - def _send_machine_vars(self, client, setting_type=None): - self.machine.bcp.transport.send_to_client( - client, bcp_command='settings', - settings=Util.convert_to_simply_type(self.machine.settings.get_settings(setting_type))) + def _send_machine_settings(self, client, setting_type=None): + settings = [setting_type] if setting_type else ["standard", "feature", "game", "coin", "hw_volume"] + for s in settings: + self.machine.bcp.transport.send_to_client( + client, bcp_command='settings', + settings=Util.convert_to_simply_type(self.machine.settings.get_settings(s))) + + def _send_machine_vars(self, client): for var_name, settings in self.machine.variables.machine_vars.items(): self.machine.bcp.transport.send_to_client(client, bcp_command='machine_variable', name=var_name, diff --git a/mpf/core/machine_vars.py b/mpf/core/machine_vars.py index 230841ca5..88f3aba92 100644 --- a/mpf/core/machine_vars.py +++ b/mpf/core/machine_vars.py @@ -93,6 +93,11 @@ def load_machine_vars(self, machine_var_data_manager: DataManager, current_time) desc: Architecture of your machine (32bit/64bit). ''' + self.set_machine_var(name="log_file_path", value=self.machine.options.get('full_logfile_path', "")) + '''machine_var: log_file_path + + desc: Absolute path of the file log for the current running game. + ''' def __getitem__(self, key): """Allow the user to access a machine variable with []. This would be used is machine.variables["var_name"].""" @@ -177,6 +182,9 @@ def set_machine_var(self, name: str, value: Any, persist=False) -> None: ---- name: String name of the variable you're setting the value for. value: The value you're setting. This can be any Type. + persist: Whether to persist this machine var to disk. Only + applies to new/unconfigured vars; vars defined in the + machine_vars config will use their config setting. """ if name not in self.machine_vars: self.configure_machine_var(name=name, persist=persist) diff --git a/mpf/devices/shot_group.py b/mpf/devices/shot_group.py index 1e5468d00..24d472fe9 100644 --- a/mpf/devices/shot_group.py +++ b/mpf/devices/shot_group.py @@ -47,7 +47,7 @@ def device_loaded_in_mode(self, mode: Mode, player: Player): self.rotation_pattern = deque(self.profile.config['rotation_pattern']) self.rotation_enabled = not self.config['enable_rotation_events'] for shot in self.config['shots']: - self.machine.events.add_handler("{}_hit".format(shot.name), self._hit) + self.machine.events.add_handler("{}_hit".format(shot.name), self._hit, shot=shot.name) self.machine.events.add_handler("player_shot_{}".format(shot.name), self._check_for_complete) def device_removed_from_mode(self, mode): @@ -145,7 +145,7 @@ def restart(self): for shot in self.config['shots']: shot.restart() - def _hit(self, advancing, **kwargs): + def _hit(self, advancing, shot, **kwargs): """One of the member shots in this shot group was hit. Args: @@ -157,12 +157,12 @@ def _hit(self, advancing, **kwargs): } """ del advancing - self.machine.events.post(self.name + '_hit') + self.machine.events.post(self.name + '_hit', shot=shot) '''event: (name)_hit desc: A member shots in the shot group called (name) has been hit. ''' - self.machine.events.post("{}_{}_hit".format(self.name, kwargs['state'])) + self.machine.events.post("{}_{}_hit".format(self.name, kwargs['state']), shot=shot) '''event: (name)_(state)_hit desc: A member shot with state (state) in the shot group (name) has been hit. diff --git a/mpf/modes/attract/code/attract.py b/mpf/modes/attract/code/attract.py index 9a735b892..90973d1c5 100644 --- a/mpf/modes/attract/code/attract.py +++ b/mpf/modes/attract/code/attract.py @@ -1,9 +1,9 @@ """Contains the Attract class which is the attract mode in a pinball machine.""" -from mpf.core.mode import Mode +from mpf.modes.carousel.code.carousel import Carousel -class Attract(Mode): +class Attract(Carousel): """Default mode running in a machine when a game is not in progress. @@ -22,6 +22,13 @@ def __init__(self, *args, **kwargs): self.start_hold_time = 0.0 self.start_buttons_held = list() + def mode_init(self): + """Initialize the mode and handle missing carousel items.""" + try: + super().mode_init() + except AssertionError: + pass + def mode_start(self, **kwargs): """Start the attract mode.""" # register switch handlers for the start button press so we can @@ -50,6 +57,10 @@ def mode_start(self, **kwargs): playfield.ball_search.enable() playfield.ball_search.start() + # Check for carousel items + if self._all_items: + super().mode_start(**kwargs) + def start_button_pressed(self): """Handle start button press. diff --git a/mpf/modes/high_score/code/high_score.py b/mpf/modes/high_score/code/high_score.py index 23aa3f7bc..065f4872a 100644 --- a/mpf/modes/high_score/code/high_score.py +++ b/mpf/modes/high_score/code/high_score.py @@ -271,6 +271,7 @@ async def _ask_player_for_initials(self, player: Player, award_label: str, value input_initials = choice(unused_initials) return input_initials + # pylint: disable-msg=too-many-arguments async def _show_award_slide(self, player_num, player_name: str, category_name: str, award: str, value: int) -> None: if not self.high_score_config['award_slide_display_time']: return diff --git a/mpf/platforms/fast/fast_audio.py b/mpf/platforms/fast/fast_audio.py index 8357a048f..9834c7a04 100644 --- a/mpf/platforms/fast/fast_audio.py +++ b/mpf/platforms/fast/fast_audio.py @@ -2,6 +2,10 @@ from math import ceil from mpf.core.logging import LogMixin +from mpf.core.settings_controller import SettingEntry + +VARIABLE_NAME = "fast_audio_%s_volume" +SETTING_TYPE = "hw_volume" class FASTAudioInterface(LogMixin): @@ -17,9 +21,9 @@ def __init__(self, platform, communicator): self.machine = platform.machine self.communicator = communicator self.amps = { - 'main': {'name': 'fast_audio_main', 'label': "Speakers"}, - 'sub': {'name': 'fast_audio_sub', 'label': "Subwoofer"}, - 'headphones': {'name': 'fast_audio_headphones', 'label': "Headphones"} + 'main': {'name': 'fast_audio_main', 'label': "Speakers", 'sort': 0}, + 'sub': {'name': 'fast_audio_sub', 'label': "Subwoofer", 'sort': 1}, + 'headphones': {'name': 'fast_audio_headphones', 'label': "Headphones", 'sort': 2} } self.control_pin_pulse_times = list() @@ -35,30 +39,41 @@ def _initialize(self, **kwargs): self._register_event_handlers() def _configure_machine_vars(self): - for amp_name in self.amps: - var_name = f'fast_audio_{amp_name}_volume' - - # Is there a race condition on first boot if the linked amp - # comes in the iteration before 'main' is written? - if amp_name != 'main' and self.communicator.config[f'link_{amp_name}_to_main']: - self.machine.variables.set_machine_var( - name=var_name, - value=self.machine.variables.get_machine_var('fast_audio_main_volume'), - persist=self.communicator.config['persist_volume_settings'] - ) - elif not self.machine.variables.is_machine_var(var_name): - self.machine.variables.set_machine_var( - name=var_name, - value=self.communicator.config[f'default_{amp_name}_volume'], - persist=self.communicator.config['persist_volume_settings'] - ) + # See if main volume has been defined yet, otherwise use default + main_volume = self.machine.variables.get_machine_var('fast_audio_main_volume') + if main_volume is None: + main_volume = self.communicator.config['default_main_volume'] + + for amp_name, settings in self.amps.items(): + + default_value = self.communicator.config[f'default_{amp_name}_volume'] + if self.communicator.config.get(f'link_{amp_name}_to_main', False): + machine_var_name = VARIABLE_NAME % "main" + else: + machine_var_name = VARIABLE_NAME % amp_name + + # Create a machine variable if one doesn't exist + if not self.machine.variables.is_machine_var(machine_var_name): + self.machine.variables.set_machine_var(machine_var_name, default_value, + self.communicator.config['persist_volume_settings']) + + # Identify the machine var for this amp + settings["machine_var"] = machine_var_name + self.machine.settings.add_setting(SettingEntry( + settings['name'], + settings['label'], + settings['sort'], + machine_var_name, + default_value, + None, + SETTING_TYPE + )) def _init_amps(self): for amp_name, amp in self.amps.items(): amp['steps'] = self.communicator.config[f'{amp_name}_steps'] amp['max_volume'] = self.communicator.config[f'max_hw_volume_{amp_name}'] amp['levels_list'] = self.communicator.config[f'{amp_name}_levels_list'] - amp['link_to_main'] = self.communicator.config[f'link_{amp_name}_to_main'] # Just set everything here. The communicator will send the # config as part of its init process later @@ -71,7 +86,8 @@ def _init_amps(self): # if we have a levels list in the config, make sure the steps num is right amp['steps'] = len(amp['levels_list']) - 1 - if amp['link_to_main'] and len(amp['levels_list']) != len(self.amps['main']['levels_list']): + if self.communicator.config[f'link_{amp_name}_to_main'] and \ + len(amp['levels_list']) != len(self.amps['main']['levels_list']): raise AssertionError(f"Cannot link {amp_name} to main. The number of volume steps must be the same. " f"Main has {len(self.amps['main']['levels_list'])} steps, " f"but {amp_name} has {len(amp['levels_list'])} steps.") @@ -135,7 +151,6 @@ def send_volume_to_hw(self, amp_name=None, send_now=True): for each_amp_name in self.amps: self.send_volume_to_hw(each_amp_name, send_now) return - self.communicator.set_volume(amp_name, self.get_volume(amp_name), send_now) def _set_volume(self, amp_name, value=0, **kwargs): @@ -155,19 +170,13 @@ def _set_volume(self, amp_name, value=0, **kwargs): #self.platform.debug_log("Writing FAST amp volume %s to %s (decimal)", amp_name, value) self.send_volume_to_hw(amp_name) - if amp_name == 'main': - for other_amp_name, other_amp in self.amps.items(): - if other_amp_name != amp_name and other_amp['link_to_main']: - # Update the machine var, which will be caught and handled - self._set_machine_var_volume(other_amp_name, value) - def get_volume(self, amp_name, **kwargs): """Return the current volume of the specified amp.""" del kwargs - return self.machine.variables.get_machine_var(f'fast_audio_{amp_name}_volume') + return self.machine.variables.get_machine_var(self.amps[amp_name]["machine_var"]) or 0 def _set_machine_var_volume(self, amp_name, value): - self.machine.variables.set_machine_var(f'fast_audio_{amp_name}_volume', value) + self.machine.variables.set_machine_var(self.amps[amp_name]["machine_var"], value) def temp_volume(self, amp_name, change=1, **kwargs): """Temporarily change the volume by the specified number of units, up or down. diff --git a/mpf/tests/machine_files/fast/config/audio.yaml b/mpf/tests/machine_files/fast/config/audio.yaml index 8a1905013..2b760e46e 100644 --- a/mpf/tests/machine_files/fast/config/audio.yaml +++ b/mpf/tests/machine_files/fast/config/audio.yaml @@ -22,6 +22,14 @@ fast: power_pulse_time: 98 reset_pulse_time: 97 +machine_vars: + fast_audio_sub_volume: + initial_value: 9 + fast_audio_main_volume: + initial_value: 8 + fast_audio_headphones_volume: + initial_value: 10 + variable_player: increase_main_volume: fast_audio_main_volume: diff --git a/mpf/tests/machine_files/fast/config/audio2.yaml b/mpf/tests/machine_files/fast/config/audio2.yaml index f90f58300..f9eda074e 100644 --- a/mpf/tests/machine_files/fast/config/audio2.yaml +++ b/mpf/tests/machine_files/fast/config/audio2.yaml @@ -27,6 +27,7 @@ fast: - 14 - 15 headphones_level: line + link_sub_to_main: true machine_vars: fast_audio_main_volume: diff --git a/mpf/tests/test_Fast_Audio.py b/mpf/tests/test_Fast_Audio.py index 7350bafb6..dc4d210ca 100644 --- a/mpf/tests/test_Fast_Audio.py +++ b/mpf/tests/test_Fast_Audio.py @@ -25,10 +25,15 @@ def create_expected_commands(self): self.serial_connections['aud'].autorespond_commands['WD:3E8'] = 'WD:3E8,03' else: - self.serial_connections['aud'].expected_commands = {'AM:0B':'AM:0B', - 'AV:08':'AV:08', - 'AS:09':'AS:09', - 'AH:0A':'AH:0A',} + self.serial_connections['aud'].expected_commands = { + # 'AV:00':'AV:00', + # 'AS:00':'AS:00', + # 'AH:00':'AH:00', + 'AM:0B':'AM:0B', + 'AV:08':'AV:08', + 'AS:09':'AS:09', + 'AH:0A':'AH:0A', + } def test_audio_basics(self): @@ -69,10 +74,6 @@ def test_audio_basics(self): self.assertTrue(fast_audio.communicator.amps['sub']['enabled']) self.assertTrue(fast_audio.communicator.amps['headphones']['enabled']) - self.assertFalse(fast_audio.amps['main']['link_to_main']) - self.assertFalse(fast_audio.amps['sub']['link_to_main']) - self.assertFalse(fast_audio.amps['headphones']['link_to_main']) - # Change the volume var and make sure it's reflected in the hardware self.aud_cpu.expected_commands['AV:0D'] = 'AV:0D' self.machine.variables.set_machine_var('fast_audio_main_volume', 13) @@ -113,9 +114,13 @@ def test_control_pins(self): @test_config('audio2.yaml') def test_machine_var_loading(self): fast_audio = self.machine.default_platform.audio_interface + self.advance_time_and_run() self.assertEqual(15, self.machine.variables.get_machine_var('fast_audio_main_volume')) + self.assertEqual(15, fast_audio.get_volume('main')) self.assertEqual(15, fast_audio.communicator.amps['main']['volume']) + # sub has a machine value set in the configs, which is persisted + self.assertEqual(2, self.machine.variables.get_machine_var('fast_audio_sub_volume')) # sub is linked to main, so it will be 15 even though the config value is 2 - self.assertEqual(15, self.machine.variables.get_machine_var('fast_audio_sub_volume')) + self.assertEqual(15, fast_audio.get_volume('main')) self.assertEqual(15, fast_audio.communicator.amps['sub']['volume']) self.assertEqual(17, self.machine.variables.get_machine_var('fast_audio_headphones_volume'))