mpf 0.81.0.dev2__py3-none-any.whl → 0.81.0.dev4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mpf/_version.py +1 -1
- mpf/assets/show.py +3 -3
- mpf/commands/game.py +27 -0
- mpf/config_players/hardware_sound_player.py +3 -3
- mpf/config_players/segment_display_player.py +4 -2
- mpf/config_spec.yaml +26 -15
- mpf/core/async_mode.py +3 -3
- mpf/core/ball_search.py +3 -3
- mpf/core/bcp/bcp.py +3 -3
- mpf/core/bcp/bcp_transport.py +3 -3
- mpf/core/config_player.py +3 -3
- mpf/core/config_spec_loader.py +4 -2
- mpf/core/config_validator.py +3 -4
- mpf/core/data_manager.py +14 -12
- mpf/core/delays.py +3 -3
- mpf/core/device.py +3 -3
- mpf/core/device_manager.py +17 -4
- mpf/core/enable_disable_mixin.py +3 -3
- mpf/core/events.py +12 -9
- mpf/core/file_interface.py +4 -4
- mpf/core/file_manager.py +16 -12
- mpf/core/logging.py +3 -4
- mpf/core/machine.py +65 -6
- mpf/core/machine_vars.py +3 -3
- mpf/core/mode.py +3 -2
- mpf/core/mpf_controller.py +4 -2
- mpf/core/placeholder_manager.py +24 -4
- mpf/core/platform.py +53 -3
- mpf/core/platform_controller.py +3 -3
- mpf/core/segment_mappings.py +3 -3
- mpf/core/text_ui.py +4 -2
- mpf/core/utility_functions.py +11 -6
- mpf/devices/achievement_group.py +3 -3
- mpf/devices/autofire.py +4 -2
- mpf/devices/ball_device/ball_device_ejector.py +4 -2
- mpf/devices/ball_device/outgoing_balls_handler.py +5 -4
- mpf/devices/ball_device/physical_ball_counter.py +3 -3
- mpf/devices/ball_routing.py +3 -3
- mpf/devices/ball_save.py +3 -3
- mpf/devices/blinkenlight.py +3 -2
- mpf/devices/dc_motor.py +90 -0
- mpf/devices/digital_output.py +3 -3
- mpf/devices/driver.py +12 -10
- mpf/devices/drop_target.py +3 -4
- mpf/devices/light.py +4 -4
- mpf/devices/light_group.py +45 -13
- mpf/devices/multiball_lock.py +3 -3
- mpf/devices/segment_display/segment_display.py +3 -3
- mpf/devices/shaker.py +12 -9
- mpf/devices/show_queue.py +3 -2
- mpf/devices/spinner.py +2 -2
- mpf/devices/switch.py +3 -3
- mpf/devices/timer.py +3 -3
- mpf/file_interfaces/pickle_interface.py +1 -1
- mpf/file_interfaces/yaml_interface.py +23 -7
- mpf/mpfconfig.yaml +2 -1
- mpf/platforms/base_serial_communicator.py +2 -3
- mpf/platforms/fadecandy.py +3 -3
- mpf/platforms/fast/communicators/base.py +92 -14
- mpf/platforms/fast/communicators/exp.py +5 -3
- mpf/platforms/fast/communicators/net_neuron.py +30 -1
- mpf/platforms/fast/communicators/seg.py +3 -2
- mpf/platforms/fast/fast.py +54 -13
- mpf/platforms/fast/fast_defines.py +5 -0
- mpf/platforms/fast/fast_driver.py +3 -2
- mpf/platforms/fast/fast_led.py +17 -7
- mpf/platforms/fast/fast_motor.py +59 -0
- mpf/platforms/fast/fast_shaker.py +1 -2
- mpf/platforms/fast/fast_switch.py +0 -2
- mpf/platforms/interfaces/motor_platform_interface.py +34 -0
- mpf/platforms/lisy/lisy.py +1 -1
- mpf/platforms/mypinballs/mypinballs.py +1 -1
- mpf/platforms/openpixel.py +3 -3
- mpf/platforms/opp/opp.py +3 -2
- mpf/platforms/opp/opp_coil.py +3 -3
- mpf/platforms/opp/opp_serial_communicator.py +3 -2
- mpf/platforms/opp/opp_servo.py +3 -2
- mpf/platforms/p3_roc.py +3 -3
- mpf/platforms/p_roc.py +3 -3
- mpf/platforms/p_roc_common.py +2 -2
- mpf/platforms/p_roc_devices.py +3 -3
- mpf/platforms/pkone/pkone_coil.py +3 -3
- mpf/platforms/pkone/pkone_lights.py +4 -2
- mpf/platforms/pkone/pkone_serial_communicator.py +4 -2
- mpf/platforms/pkone/pkone_servo.py +3 -2
- mpf/platforms/pkone/pkone_switch.py +3 -2
- mpf/platforms/pololu_maestro.py +1 -1
- mpf/platforms/smart_virtual.py +4 -3
- mpf/platforms/spike/spike.py +3 -3
- mpf/platforms/system11.py +3 -3
- mpf/platforms/virtual.py +36 -2
- mpf/platforms/virtual_pinball/virtual_pinball.py +1 -1
- mpf/plugins/auditor.py +3 -2
- mpf/plugins/virtual_segment_display_connector.py +3 -2
- mpf/tests/machine_files/auditor/config/config.yaml +3 -1
- mpf/tests/machine_files/custom_code/code/test_code.py +4 -0
- mpf/tests/machine_files/data_manager/config/config.yaml +4 -1
- mpf/tests/machine_files/dc_motor/config/config.yaml +18 -0
- mpf/tests/machine_files/event_manager/config/test_event_manager.yaml +6 -0
- mpf/tests/machine_files/event_manager/modes/game_mode/config/game_mode.yaml +5 -0
- mpf/tests/machine_files/light/config/light_groups.yaml +12 -0
- mpf/tests/machine_files/service_mode/config/config.yaml +1 -1
- mpf/tests/machine_files/settings/config/config.yaml +27 -0
- mpf/tests/machine_files/soft_power_down/config/config.yaml +1 -0
- mpf/tests/platforms/fast.py +3 -3
- mpf/tests/test_CustomCode.py +7 -0
- mpf/tests/test_DCMotor.py +37 -0
- mpf/tests/test_DeviceManager.py +63 -0
- mpf/tests/test_EventManager.py +45 -0
- mpf/tests/test_Fast_Communicator.py +183 -0
- mpf/tests/test_Fast_Led.py +61 -0
- mpf/tests/test_Fast_Power.py +82 -0
- mpf/tests/test_FileManager.py +75 -0
- mpf/tests/test_LightGroups.py +48 -16
- mpf/tests/test_LightNumbering.py +2 -3
- mpf/tests/test_RandomEventPlayer.py +4 -4
- mpf/tests/test_ServiceCli.py +1 -1
- mpf/tests/test_Settings.py +16 -0
- mpf/tests/test_SoftPowerDown.py +69 -0
- mpf/tests/test_Utility_Functions.py +54 -7
- mpf/tests/test_YamlInterface.py +33 -0
- {mpf-0.81.0.dev2.dist-info → mpf-0.81.0.dev4.dist-info}/METADATA +2 -2
- {mpf-0.81.0.dev2.dist-info → mpf-0.81.0.dev4.dist-info}/RECORD +128 -122
- {mpf-0.81.0.dev2.dist-info → mpf-0.81.0.dev4.dist-info}/WHEEL +1 -1
- mpf/plugins/twitch/__init__.py +0 -1
- mpf/plugins/twitch/twitch_client.py +0 -254
- mpf/plugins/twitch_bot.py +0 -57
- mpf/tests/machine_files/twitch_client/config/config.yaml +0 -6
- mpf/tests/test_TwitchClient.py +0 -175
- {mpf-0.81.0.dev2.dist-info → mpf-0.81.0.dev4.dist-info}/entry_points.txt +0 -0
- {mpf-0.81.0.dev2.dist-info → mpf-0.81.0.dev4.dist-info}/licenses/AUTHORS.md +0 -0
- {mpf-0.81.0.dev2.dist-info → mpf-0.81.0.dev4.dist-info}/licenses/LICENSE.md +0 -0
- {mpf-0.81.0.dev2.dist-info → mpf-0.81.0.dev4.dist-info}/top_level.txt +0 -0
mpf/_version.py
CHANGED
mpf/assets/show.py
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
import re
|
|
3
3
|
from collections import namedtuple
|
|
4
4
|
|
|
5
|
-
from typing import List, Dict, Any, Optional
|
|
5
|
+
from typing import List, Dict, Any, Optional, TYPE_CHECKING
|
|
6
6
|
|
|
7
7
|
from mpf.core.assets import AssetPool
|
|
8
8
|
from mpf.core.config_validator import RuntimeToken
|
|
9
9
|
from mpf.core.utility_functions import Util
|
|
10
10
|
from mpf.exceptions.config_file_error import ConfigFileError
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
if
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
14
|
from typing import NoReturn # pylint: disable-msg=cyclic-import,unused-import
|
|
15
15
|
|
|
16
16
|
__api__ = ['Show', 'RunningShow', 'ShowPool']
|
mpf/commands/game.py
CHANGED
|
@@ -169,6 +169,8 @@ class Command:
|
|
|
169
169
|
console_log.setFormatter(logging.Formatter(
|
|
170
170
|
'%(asctime)s.%(msecs)03d : %(levelname)s [%(name)s] %(message)s', "%H:%M:%S"))
|
|
171
171
|
|
|
172
|
+
console_log.addFilter(ConsoleCustomLevelFilter())
|
|
173
|
+
|
|
172
174
|
# initialize async handler for console
|
|
173
175
|
console_log_queue = Queue()
|
|
174
176
|
console_queue_handler = QueueHandler(console_log_queue)
|
|
@@ -183,6 +185,7 @@ class Command:
|
|
|
183
185
|
else:
|
|
184
186
|
formatter = logging.Formatter('%(asctime)s : %(levelname)s : %(name)s : %(message)s')
|
|
185
187
|
file_log.setFormatter(formatter)
|
|
188
|
+
file_log.addFilter(FileCustomLevelFilter())
|
|
186
189
|
|
|
187
190
|
# initialize async handler for file log
|
|
188
191
|
file_log_queue = Queue()
|
|
@@ -237,6 +240,8 @@ class Command:
|
|
|
237
240
|
def sigint_handler(self, signum=None, frame=None):
|
|
238
241
|
"""Handle SIGINT."""
|
|
239
242
|
del signum, frame
|
|
243
|
+
if self.machine:
|
|
244
|
+
self.machine.error_log("SIGINT received")
|
|
240
245
|
self._sigint_count += 1
|
|
241
246
|
if self._sigint_count > 1:
|
|
242
247
|
self.exit("Received second SIGINT. Will exit ungracefully!")
|
|
@@ -273,3 +278,25 @@ class Command:
|
|
|
273
278
|
input('Press ENTER to continue...') # nosec
|
|
274
279
|
|
|
275
280
|
sys.exit()
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class ConsoleCustomLevelFilter(logging.Filter):
|
|
284
|
+
|
|
285
|
+
"""Filters out custom levels meant strictly for files."""
|
|
286
|
+
|
|
287
|
+
def filter(self, record):
|
|
288
|
+
"""Filters: 11 = DEBUG (File Only), 21 = INFO (File Only)."""
|
|
289
|
+
if record.levelno in (11, 21):
|
|
290
|
+
return False
|
|
291
|
+
return True
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
class FileCustomLevelFilter(logging.Filter):
|
|
295
|
+
|
|
296
|
+
"""Filters out custom levels meant strictly for console."""
|
|
297
|
+
|
|
298
|
+
def filter(self, record):
|
|
299
|
+
"""Filters: 12 = DEBUG (Console Only), 22 = INFO (Console Only)."""
|
|
300
|
+
if record.levelno in (12, 22):
|
|
301
|
+
return False
|
|
302
|
+
return True
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"""Config player for sounds on an external sound card."""
|
|
2
|
-
from typing import List
|
|
2
|
+
from typing import List, TYPE_CHECKING
|
|
3
3
|
|
|
4
4
|
from mpf.config_players.device_config_player import DeviceConfigPlayer
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
if
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
8
|
from mpf.devices.hardware_sound_system import HardwareSoundSystem # pylint: disable-msg=cyclic-import,unused-import; # noqa
|
|
9
9
|
|
|
10
10
|
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"""Config player for text on segment displays."""
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
2
4
|
from mpf.core.delays import DelayManager
|
|
3
5
|
|
|
4
6
|
from mpf.config_players.device_config_player import DeviceConfigPlayer
|
|
5
7
|
from mpf.platforms.interfaces.segment_display_platform_interface import FlashingType
|
|
6
8
|
from mpf.devices.segment_display.transition_manager import TransitionManager
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
if
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
10
12
|
from typing import Dict # pylint: disable-msg=cyclic-import,unused-import
|
|
11
13
|
from mpf.devices.segment_display.segment_display import \
|
|
12
14
|
SegmentDisplay # pylint: disable-msg=cyclic-import,unused-import
|
mpf/config_spec.yaml
CHANGED
|
@@ -83,8 +83,8 @@ auditor:
|
|
|
83
83
|
audit: list|str|None
|
|
84
84
|
autosave: single|bool|true
|
|
85
85
|
autosave_events: single|bool|true
|
|
86
|
-
events:
|
|
87
|
-
player:
|
|
86
|
+
events: set|event_handler|None
|
|
87
|
+
player: set|str|None
|
|
88
88
|
audit_shots: single|bool|true
|
|
89
89
|
missing_switch_min_games: single|int|10
|
|
90
90
|
num_player_top_records: single|int|1
|
|
@@ -311,6 +311,18 @@ coils:
|
|
|
311
311
|
custom_code:
|
|
312
312
|
__valid_in__: machine
|
|
313
313
|
__type__: list
|
|
314
|
+
dc_motors:
|
|
315
|
+
__valid_in__: machine
|
|
316
|
+
__type__: device
|
|
317
|
+
default_power: single|float|0.0
|
|
318
|
+
number: single|str|
|
|
319
|
+
control_events: dict|str:subconfig(dc_motors_pulse_settings)|None
|
|
320
|
+
platform: single|str|None
|
|
321
|
+
platform_settings: single|dict|None
|
|
322
|
+
dc_motors_pulse_settings:
|
|
323
|
+
action: single|enum(pulse,reverse_pulse,stop)|pulse
|
|
324
|
+
power: single|float|None
|
|
325
|
+
duration: single|template_secs|None
|
|
314
326
|
digital_outputs:
|
|
315
327
|
__valid_in__: machine
|
|
316
328
|
__type__: device
|
|
@@ -544,7 +556,7 @@ fast:
|
|
|
544
556
|
__type__: config
|
|
545
557
|
net: single|subconfig(fast_net)|None
|
|
546
558
|
exp: single|subconfig(fast_exp)|None
|
|
547
|
-
exp_int: single|subconfig(fast_exp)|None
|
|
559
|
+
exp_int: single|subconfig(fast_exp)|None
|
|
548
560
|
aud: single|subconfig(fast_aud)|None
|
|
549
561
|
seg: single|subconfig(fast_seg)|None
|
|
550
562
|
dmd: single|subconfig(fast_dmd)|None
|
|
@@ -561,7 +573,7 @@ fast_net:
|
|
|
561
573
|
console_log: single|enum(none,basic,full)|none
|
|
562
574
|
file_log: single|enum(none,basic,full)|basic
|
|
563
575
|
controller: single|enum(neuron,nano,sys11,wpc89,wpc95)|
|
|
564
|
-
watchdog: single|ms|
|
|
576
|
+
watchdog: single|ms|500
|
|
565
577
|
default_quick_debounce_open: single|ms|2
|
|
566
578
|
default_quick_debounce_close: single|ms|2
|
|
567
579
|
default_normal_debounce_open: single|ms|4
|
|
@@ -569,6 +581,8 @@ fast_net:
|
|
|
569
581
|
mute_unconfigured_switches: single|bool|false
|
|
570
582
|
gi_hz: single|int|30
|
|
571
583
|
lamp_hz: single|int|30
|
|
584
|
+
soft_power_hold_ms: single|ms|2500
|
|
585
|
+
soft_power_powerdown_delay: single|ms|4000
|
|
572
586
|
fast_io_loop:
|
|
573
587
|
model: single|str| # FP-I/O-0804, etc.
|
|
574
588
|
order: single|enum(1,2,3,4,5,6,7,8,9)|
|
|
@@ -736,6 +750,7 @@ hardware:
|
|
|
736
750
|
i2c: list|str|default
|
|
737
751
|
stepper_controllers: list|str|default
|
|
738
752
|
shaker_controllers: list|str|default
|
|
753
|
+
motor_controllers: list|str|default
|
|
739
754
|
hardware_sound_system: list|str|default
|
|
740
755
|
hardware_sound_systems:
|
|
741
756
|
__valid_in__: machine
|
|
@@ -834,7 +849,7 @@ neoseg_displays:
|
|
|
834
849
|
number_template: single|str|None
|
|
835
850
|
start_x: single|float|None
|
|
836
851
|
start_y: single|float|None
|
|
837
|
-
size: single|enum(2digit,8digit)|8digit
|
|
852
|
+
size: single|enum(2digit,8digit,2digit-rg-swapped,8digit-rg-swapped)|8digit
|
|
838
853
|
light_template: single|subconfig(lights,device)|
|
|
839
854
|
lights:
|
|
840
855
|
__valid_in__: machine
|
|
@@ -929,6 +944,7 @@ machine:
|
|
|
929
944
|
__type__: config
|
|
930
945
|
balls_installed: single|int|1
|
|
931
946
|
min_balls: single|int|1
|
|
947
|
+
soft_shutdown_exit_command: single|str|None
|
|
932
948
|
machine_vars:
|
|
933
949
|
__valid_in__: machine
|
|
934
950
|
__type__: config_dict
|
|
@@ -1013,6 +1029,10 @@ mpf:
|
|
|
1013
1029
|
paths: ignore
|
|
1014
1030
|
report_crashes: single|enum(ask,never,always)|never
|
|
1015
1031
|
rgbw_white_behavior: single|enum(white_only,min_rgb,duck_rgb)|duck_rgb
|
|
1032
|
+
data_manager:
|
|
1033
|
+
__valid_in__: machine
|
|
1034
|
+
__type__: config
|
|
1035
|
+
use_fsync: single|bool|None
|
|
1016
1036
|
multiballs:
|
|
1017
1037
|
__valid_in__: machine, mode
|
|
1018
1038
|
__type__: device
|
|
@@ -1414,7 +1434,7 @@ settings:
|
|
|
1414
1434
|
label: single|str|
|
|
1415
1435
|
sort: single|int|
|
|
1416
1436
|
values: dict|str:str|
|
|
1417
|
-
key_type: single|enum(str,float,int)|str
|
|
1437
|
+
key_type: single|enum(str,float,int,bool)|str
|
|
1418
1438
|
default: single|str|
|
|
1419
1439
|
machine_var: single|str|None
|
|
1420
1440
|
settingType: single|str|standard
|
|
@@ -1831,15 +1851,6 @@ trinamics_steprocker:
|
|
|
1831
1851
|
__valid_in__: machine
|
|
1832
1852
|
__type__: config
|
|
1833
1853
|
port: single|str|
|
|
1834
|
-
twitch_client:
|
|
1835
|
-
__valid_in__: machine
|
|
1836
|
-
__type__: config
|
|
1837
|
-
user: single|str|None
|
|
1838
|
-
password: single|str|None
|
|
1839
|
-
channel: single|str|None
|
|
1840
|
-
user_var: single|str|None
|
|
1841
|
-
password_var: single|str|None
|
|
1842
|
-
channel_var: single|str|None
|
|
1843
1854
|
variable_player:
|
|
1844
1855
|
__valid_in__: machine, mode
|
|
1845
1856
|
__type__: config_player
|
mpf/core/async_mode.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
"""Base class for asyncio modes."""
|
|
2
|
-
from typing import Optional
|
|
2
|
+
from typing import Optional, TYPE_CHECKING
|
|
3
3
|
|
|
4
4
|
import abc
|
|
5
5
|
import asyncio
|
|
6
6
|
|
|
7
7
|
from mpf.core.mode import Mode
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
if
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
11
|
from mpf.core.machine import MachineController # pylint: disable-msg=cyclic-import,unused-import
|
|
12
12
|
|
|
13
13
|
|
mpf/core/ball_search.py
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
"""Implements the ball search procedure."""
|
|
2
2
|
from collections import namedtuple
|
|
3
3
|
|
|
4
|
-
from typing import List
|
|
4
|
+
from typing import List, TYPE_CHECKING
|
|
5
5
|
|
|
6
6
|
from mpf.core.delays import DelayManager
|
|
7
7
|
from mpf.core.machine import MachineController
|
|
8
8
|
from mpf.core.mpf_controller import MpfController
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
if
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
12
|
from mpf.devices.playfield import Playfield # pylint: disable-msg=cyclic-import,unused-import
|
|
13
13
|
|
|
14
14
|
BallSearchCallback = namedtuple("BallSearchCallback", ["priority", "callback", "name", "restore_callback"])
|
mpf/core/bcp/bcp.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""BCP module."""
|
|
2
2
|
import asyncio
|
|
3
3
|
from functools import partial
|
|
4
|
-
from typing import List
|
|
4
|
+
from typing import List, TYPE_CHECKING
|
|
5
5
|
|
|
6
6
|
from mpf.core.events import QueuedEvent
|
|
7
7
|
from mpf.core.mpf_controller import MpfController
|
|
@@ -12,8 +12,8 @@ from mpf.core.utility_functions import Util
|
|
|
12
12
|
from mpf.core.bcp.bcp_interface import BcpInterface
|
|
13
13
|
from mpf.core.bcp.bcp_transport import BcpTransportManager
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
if
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
17
|
from mpf.core.machine import MachineController # pylint: disable-msg=cyclic-import,unused-import
|
|
18
18
|
|
|
19
19
|
|
mpf/core/bcp/bcp_transport.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
"""Classes which manage BCP transports."""
|
|
2
2
|
from collections import defaultdict
|
|
3
3
|
|
|
4
|
-
from typing import Union
|
|
4
|
+
from typing import Union, TYPE_CHECKING
|
|
5
5
|
|
|
6
6
|
from mpf.core.bcp.bcp_client import BaseBcpClient
|
|
7
7
|
from mpf.core.utility_functions import Util
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
if
|
|
9
|
+
# noqa
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
11
|
from mpf.core.machine import MachineController # pylint: disable-msg=cyclic-import,unused-import
|
|
12
12
|
|
|
13
13
|
|
mpf/core/config_player.py
CHANGED
|
@@ -3,7 +3,7 @@ import abc
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import re
|
|
5
5
|
from functools import partial
|
|
6
|
-
from typing import List
|
|
6
|
+
from typing import List, TYPE_CHECKING
|
|
7
7
|
|
|
8
8
|
from mpf.core.machine import MachineController
|
|
9
9
|
from mpf.core.mode import Mode
|
|
@@ -11,8 +11,8 @@ from mpf.core.logging import LogMixin
|
|
|
11
11
|
from mpf.exceptions.config_file_error import ConfigFileError
|
|
12
12
|
from mpf.core.placeholder_manager import ConditionalEvent
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
if
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
16
|
from mpf.core.placeholder_manager import BoolTemplate # pylint: disable-msg=cyclic-import,unused-import
|
|
17
17
|
from typing import Dict # pylint: disable-msg=cyclic-import,unused-import
|
|
18
18
|
|
mpf/core/config_spec_loader.py
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
"""Parse config_spec."""
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
2
4
|
from pkg_resources import iter_entry_points
|
|
3
5
|
|
|
4
6
|
from mpf.core.utility_functions import Util
|
|
5
7
|
from mpf.file_interfaces.yaml_interface import YamlInterface
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
if
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
9
11
|
from mpf.core.device import Device # pylint: disable-msg=cyclic-import,unused-import
|
|
10
12
|
|
|
11
13
|
|
mpf/core/config_validator.py
CHANGED
|
@@ -6,8 +6,7 @@ import re
|
|
|
6
6
|
from collections import namedtuple
|
|
7
7
|
from copy import deepcopy
|
|
8
8
|
|
|
9
|
-
from typing import Any
|
|
10
|
-
from typing import Dict
|
|
9
|
+
from typing import Any, Dict, TYPE_CHECKING
|
|
11
10
|
|
|
12
11
|
from mpf.core.config_spec_loader import ConfigSpecLoader
|
|
13
12
|
from mpf.core.rgb_color import NAMED_RGB_COLORS, RGBColor
|
|
@@ -15,8 +14,8 @@ from mpf.exceptions.config_file_error import ConfigFileError
|
|
|
15
14
|
from mpf.file_interfaces.yaml_interface import YamlInterface
|
|
16
15
|
from mpf.core.utility_functions import Util
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
if
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
20
19
|
from mpf.core.machine import MachineController # pylint: disable-msg=cyclic-import,unused-import
|
|
21
20
|
|
|
22
21
|
|
mpf/core/data_manager.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import copy
|
|
4
4
|
import os
|
|
5
5
|
import errno
|
|
6
|
+
import sys
|
|
6
7
|
import threading
|
|
7
8
|
import time
|
|
8
9
|
import _thread
|
|
@@ -17,10 +18,10 @@ class DataManager(MpfController):
|
|
|
17
18
|
|
|
18
19
|
config_name = "data_manager"
|
|
19
20
|
|
|
20
|
-
__slots__ = ["name", "min_wait_secs", "filename", "data", "_dirty"]
|
|
21
|
+
__slots__ = ["config", "name", "min_wait_secs", "filename", "data", "_dirty", "use_fsync"]
|
|
21
22
|
|
|
22
23
|
def __init__(self, machine, name, min_wait_secs=1):
|
|
23
|
-
"""Initialize data
|
|
24
|
+
"""Initialize data manager.
|
|
24
25
|
|
|
25
26
|
The DataManager is responsible for reading and writing data to/from a
|
|
26
27
|
file on disk.
|
|
@@ -48,6 +49,14 @@ class DataManager(MpfController):
|
|
|
48
49
|
else:
|
|
49
50
|
raise AssertionError("Invalid path {} for {}".format(config_path, name))
|
|
50
51
|
|
|
52
|
+
self.config = self.machine.config_validator.validate_config(
|
|
53
|
+
"data_manager", self.machine.config.get('data_manager'))
|
|
54
|
+
|
|
55
|
+
maybe_use_fsync = self.config.get('use_fsync', None)
|
|
56
|
+
if maybe_use_fsync is None:
|
|
57
|
+
maybe_use_fsync = sys.platform != 'win32'
|
|
58
|
+
self.use_fsync = maybe_use_fsync
|
|
59
|
+
|
|
51
60
|
self.data = dict()
|
|
52
61
|
self._dirty = threading.Event()
|
|
53
62
|
|
|
@@ -116,29 +125,22 @@ class DataManager(MpfController):
|
|
|
116
125
|
|
|
117
126
|
def _writing_thread(self): # pragma: no cover
|
|
118
127
|
# prevent early writes at start-up
|
|
119
|
-
data = None
|
|
120
128
|
time.sleep(self.min_wait_secs)
|
|
121
129
|
while not self.machine.thread_stopper.is_set():
|
|
122
130
|
if not self._dirty.wait(1):
|
|
123
131
|
continue
|
|
124
|
-
while FileManager.is_busy:
|
|
125
|
-
time.sleep(0.2)
|
|
126
132
|
self._dirty.clear()
|
|
127
133
|
|
|
128
|
-
data = copy.deepcopy(self.data)
|
|
129
134
|
# save data
|
|
130
135
|
try:
|
|
131
|
-
FileManager.save(self.filename, data)
|
|
136
|
+
FileManager.save(self.filename, copy.deepcopy(self.data), self.use_fsync)
|
|
132
137
|
except Exception as e: # pylint: disable=broad-exception-caught
|
|
133
138
|
# If the file writer has an exception handle it here. Otherwise
|
|
134
139
|
# this thread will die and all subsequent write attempts will no-op.
|
|
135
140
|
self.info_log("ERROR writing file %s: %s", self.filename, e)
|
|
136
|
-
data = None
|
|
137
141
|
# prevent too many writes
|
|
138
142
|
time.sleep(self.min_wait_secs)
|
|
139
143
|
|
|
140
144
|
# if dirty write data one last time during shutdown
|
|
141
|
-
if
|
|
142
|
-
|
|
143
|
-
time.sleep(0.2)
|
|
144
|
-
FileManager.save(self.filename, data)
|
|
145
|
+
if self._dirty.is_set():
|
|
146
|
+
FileManager.save(self.filename, copy.deepcopy(self.data), self.use_fsync)
|
mpf/core/delays.py
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
import uuid
|
|
4
4
|
from functools import partial
|
|
5
|
-
from typing import Any, Callable, Dict, Tuple
|
|
5
|
+
from typing import Any, Callable, Dict, Tuple, TYPE_CHECKING
|
|
6
6
|
from mpf.core.mpf_controller import MpfController
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
if
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
10
|
from mpf.core.machine import MachineController # pylint: disable-msg=cyclic-import,unused-import
|
|
11
11
|
|
|
12
12
|
__api__ = ['DelayManager']
|
mpf/core/device.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
"""Contains the Device base class."""
|
|
2
2
|
import abc
|
|
3
3
|
|
|
4
|
-
from typing import List, Any, Optional
|
|
4
|
+
from typing import List, Any, Optional, TYPE_CHECKING
|
|
5
5
|
|
|
6
6
|
from mpf.core.machine import MachineController
|
|
7
7
|
from mpf.core.logging import LogMixin
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
if
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
11
|
from mpf.core.mode import Mode # pylint: disable-msg=cyclic-import,unused-import
|
|
12
12
|
from mpf.core.platform import BasePlatform # pylint: disable-msg=cyclic-import,unused-import; # noqa
|
|
13
13
|
|
mpf/core/device_manager.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
"""Contains the DeviceManager base class."""
|
|
2
2
|
import asyncio
|
|
3
3
|
|
|
4
|
-
from typing import Callable, Tuple, List, Generator, Dict
|
|
4
|
+
from typing import Callable, Tuple, List, Generator, Dict, TYPE_CHECKING
|
|
5
5
|
|
|
6
6
|
from mpf.core.utility_functions import Util
|
|
7
7
|
from mpf.core.mpf_controller import MpfController
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
if
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
11
|
from mpf.core.device import Device # pylint: disable-msg=cyclic-import,unused-import
|
|
12
12
|
|
|
13
13
|
|
|
@@ -172,7 +172,20 @@ class DeviceManager(MpfController):
|
|
|
172
172
|
for device_name in config:
|
|
173
173
|
futures.append(collection[device_name].device_added_system_wide())
|
|
174
174
|
|
|
175
|
-
|
|
175
|
+
fs = [asyncio.create_task(future) for future in futures]
|
|
176
|
+
done, pending = await asyncio.wait(fs, return_when=asyncio.FIRST_EXCEPTION)
|
|
177
|
+
|
|
178
|
+
# Check if any of the futures returned an exception and throw it
|
|
179
|
+
for task in done:
|
|
180
|
+
if task.cancelled():
|
|
181
|
+
continue
|
|
182
|
+
|
|
183
|
+
exc = task.exception()
|
|
184
|
+
if exc is not None:
|
|
185
|
+
for pending_task in pending:
|
|
186
|
+
pending_task.cancel()
|
|
187
|
+
await asyncio.gather(*pending, return_exceptions=True)
|
|
188
|
+
raise exc
|
|
176
189
|
|
|
177
190
|
# pylint: disable-msg=too-many-nested-blocks
|
|
178
191
|
def get_device_control_events(self, config) -> Generator[Tuple[str, Callable, int, "Device"], None, None]:
|
mpf/core/enable_disable_mixin.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""Implements enable and disable events for devices."""
|
|
2
|
-
from typing import Optional
|
|
2
|
+
from typing import Optional, TYPE_CHECKING
|
|
3
3
|
|
|
4
4
|
import abc
|
|
5
5
|
|
|
@@ -12,8 +12,8 @@ from mpf.core.events import event_handler
|
|
|
12
12
|
from mpf.core.mode_device import ModeDevice
|
|
13
13
|
from mpf.core.machine import MachineController
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
if
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
17
|
from mpf.core.mode import Mode # pylint: disable-msg=cyclic-import,unused-import
|
|
18
18
|
from mpf.core.player import Player # pylint: disable-msg=cyclic-import,unused-import
|
|
19
19
|
|
mpf/core/events.py
CHANGED
|
@@ -7,12 +7,12 @@ import asyncio
|
|
|
7
7
|
from functools import partial, lru_cache
|
|
8
8
|
from unittest.mock import MagicMock
|
|
9
9
|
|
|
10
|
-
from typing import Dict, Any, Tuple, Optional, Callable, List
|
|
10
|
+
from typing import Dict, Any, Tuple, Optional, Callable, List, TYPE_CHECKING
|
|
11
11
|
|
|
12
12
|
from mpf.core.mpf_controller import MpfController
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
if
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
16
|
from mpf.core.machine import MachineController # pylint: disable-msg=cyclic-import,unused-import
|
|
17
17
|
from mpf.core.placeholder_manager import BaseTemplate # pylint: disable-msg=cyclic-import,unused-import
|
|
18
18
|
from typing import Deque # pylint: disable-msg=cyclic-import,unused-import
|
|
@@ -224,14 +224,15 @@ class EventManager(MpfController):
|
|
|
224
224
|
'handler you passed?'.format(handler, event))
|
|
225
225
|
|
|
226
226
|
sig = inspect.signature(handler)
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if sig.parameters['kwargs'].kind != inspect.Parameter.VAR_KEYWORD:
|
|
232
|
-
raise AssertionError("Handler {} for event '{}' param kwargs is missing '**'. "
|
|
227
|
+
catchall_name = next((name for name in ('kwargs', '_kwargs') if name in sig.parameters), None)
|
|
228
|
+
if catchall_name is None:
|
|
229
|
+
raise AssertionError("Handler {} for event '{}' is missing **kwargs. "
|
|
233
230
|
"Actual signature: {}".format(handler, event, sig))
|
|
234
231
|
|
|
232
|
+
if sig.parameters[catchall_name].kind != inspect.Parameter.VAR_KEYWORD:
|
|
233
|
+
raise AssertionError("Handler {} for event '{}' param {} is missing '**'. "
|
|
234
|
+
"Actual signature: {}".format(handler, event, catchall_name, sig))
|
|
235
|
+
|
|
235
236
|
event, condition, additional_priority = self.get_event_and_condition_from_string(event)
|
|
236
237
|
priority += additional_priority
|
|
237
238
|
|
|
@@ -457,6 +458,8 @@ class EventManager(MpfController):
|
|
|
457
458
|
_future=future,
|
|
458
459
|
_keys=keys,
|
|
459
460
|
event=event_name)))
|
|
461
|
+
|
|
462
|
+
future.add_done_callback(lambda f: [self.remove_handler_by_key(k) for k in keys])
|
|
460
463
|
return future
|
|
461
464
|
|
|
462
465
|
def _wait_handler(self, _future: asyncio.Future, _keys: List[EventHandlerKey], **kwargs):
|
mpf/core/file_interface.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"""Interface for config file loaders."""
|
|
2
2
|
import logging
|
|
3
3
|
import os
|
|
4
|
-
from typing import Tuple, Optional
|
|
4
|
+
from typing import Tuple, Optional, TYPE_CHECKING
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
if
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
8
|
from typing import List # pylint: disable-msg=cyclic-import,unused-import
|
|
9
9
|
|
|
10
10
|
|
|
@@ -48,6 +48,6 @@ class FileInterface:
|
|
|
48
48
|
"""Load file."""
|
|
49
49
|
raise NotImplementedError
|
|
50
50
|
|
|
51
|
-
def save(self, filename, data):
|
|
51
|
+
def save(self, filename, data, use_fsync):
|
|
52
52
|
"""Save file."""
|
|
53
53
|
raise NotImplementedError
|
mpf/core/file_manager.py
CHANGED
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
5
|
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
6
8
|
from mpf.file_interfaces.pickle_interface import PickleInterface
|
|
7
9
|
from mpf.file_interfaces.yaml_interface import YamlInterface
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
if
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
11
13
|
from typing import Dict, List # pylint: disable-msg=cyclic-import,unused-import
|
|
12
14
|
|
|
13
15
|
|
|
@@ -20,7 +22,6 @@ class FileManager:
|
|
|
20
22
|
log = logging.getLogger('FileManager')
|
|
21
23
|
file_interfaces = dict() # type: Dict[str, YamlInterface]
|
|
22
24
|
initialized = False
|
|
23
|
-
is_busy = False
|
|
24
25
|
|
|
25
26
|
@classmethod
|
|
26
27
|
def init(cls):
|
|
@@ -105,27 +106,30 @@ class FileManager:
|
|
|
105
106
|
return interface.load(file, verify_version, halt_on_error)
|
|
106
107
|
|
|
107
108
|
@staticmethod
|
|
108
|
-
def save(filename, data):
|
|
109
|
+
def save(filename, data, use_fsync):
|
|
109
110
|
"""Save data to file."""
|
|
110
111
|
if not FileManager.initialized:
|
|
111
112
|
FileManager.init()
|
|
112
113
|
|
|
113
|
-
# FileManager is a singleton and many threads may attempt to write
|
|
114
|
-
# concurrently. Ruamel has a known issue where concurrent writes
|
|
115
|
-
# on a YamlInterface will throw. Set a flag to prevent multiple
|
|
116
|
-
# data writes concurrently.
|
|
117
|
-
# TODO: Create FileManager instances for each DataManager instance.
|
|
118
|
-
FileManager.is_busy = True
|
|
119
114
|
ext = os.path.splitext(filename)[1]
|
|
120
115
|
|
|
121
116
|
# save to temp file and move afterwards. prevents broken files
|
|
122
117
|
temp_file = os.path.dirname(filename) + os.sep + "_" + os.path.basename(filename)
|
|
123
118
|
|
|
124
119
|
try:
|
|
125
|
-
FileManager.file_interfaces[ext].save(temp_file, data)
|
|
120
|
+
FileManager.file_interfaces[ext].save(temp_file, data, use_fsync)
|
|
126
121
|
except KeyError:
|
|
127
122
|
raise AssertionError("No config file processor available for file type {}".format(ext))
|
|
128
123
|
|
|
129
124
|
# move temp file
|
|
130
125
|
os.replace(temp_file, filename)
|
|
131
|
-
|
|
126
|
+
|
|
127
|
+
if use_fsync:
|
|
128
|
+
try:
|
|
129
|
+
directory = os.open(os.path.dirname(os.path.abspath(filename)), os.O_RDONLY)
|
|
130
|
+
try:
|
|
131
|
+
os.fsync(directory)
|
|
132
|
+
finally:
|
|
133
|
+
os.close(directory)
|
|
134
|
+
except (OSError, TypeError) as e:
|
|
135
|
+
FileManager.log.debug("Directory fsync failed for file %s - %s", filename, e)
|