mpf 0.81.0.dev2__py3-none-any.whl → 0.81.0.dev3__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.
Files changed (33) hide show
  1. mpf/_version.py +1 -1
  2. mpf/config_spec.yaml +3 -3
  3. mpf/core/device_manager.py +14 -1
  4. mpf/core/events.py +9 -6
  5. mpf/core/placeholder_manager.py +21 -1
  6. mpf/core/utility_functions.py +11 -6
  7. mpf/devices/light_group.py +45 -13
  8. mpf/platforms/fast/fast.py +7 -4
  9. mpf/platforms/fast/fast_led.py +17 -7
  10. mpf/platforms/virtual_pinball/virtual_pinball.py +1 -1
  11. mpf/tests/machine_files/auditor/config/config.yaml +1 -1
  12. mpf/tests/machine_files/custom_code/code/test_code.py +4 -0
  13. mpf/tests/machine_files/event_manager/config/test_event_manager.yaml +6 -0
  14. mpf/tests/machine_files/event_manager/modes/game_mode/config/game_mode.yaml +5 -0
  15. mpf/tests/machine_files/light/config/light_groups.yaml +12 -0
  16. mpf/tests/machine_files/service_mode/config/config.yaml +1 -1
  17. mpf/tests/machine_files/settings/config/config.yaml +27 -0
  18. mpf/tests/test_CustomCode.py +7 -0
  19. mpf/tests/test_DeviceManager.py +63 -0
  20. mpf/tests/test_EventManager.py +45 -0
  21. mpf/tests/test_Fast_Led.py +61 -0
  22. mpf/tests/test_LightGroups.py +48 -16
  23. mpf/tests/test_LightNumbering.py +2 -3
  24. mpf/tests/test_RandomEventPlayer.py +4 -4
  25. mpf/tests/test_Settings.py +16 -0
  26. mpf/tests/test_Utility_Functions.py +54 -7
  27. {mpf-0.81.0.dev2.dist-info → mpf-0.81.0.dev3.dist-info}/METADATA +2 -2
  28. {mpf-0.81.0.dev2.dist-info → mpf-0.81.0.dev3.dist-info}/RECORD +33 -32
  29. {mpf-0.81.0.dev2.dist-info → mpf-0.81.0.dev3.dist-info}/WHEEL +1 -1
  30. {mpf-0.81.0.dev2.dist-info → mpf-0.81.0.dev3.dist-info}/entry_points.txt +0 -0
  31. {mpf-0.81.0.dev2.dist-info → mpf-0.81.0.dev3.dist-info}/licenses/AUTHORS.md +0 -0
  32. {mpf-0.81.0.dev2.dist-info → mpf-0.81.0.dev3.dist-info}/licenses/LICENSE.md +0 -0
  33. {mpf-0.81.0.dev2.dist-info → mpf-0.81.0.dev3.dist-info}/top_level.txt +0 -0
mpf/_version.py CHANGED
@@ -10,7 +10,7 @@ PyPI.
10
10
 
11
11
  """
12
12
 
13
- __version__ = '0.81.0.dev2'
13
+ __version__ = '0.81.0.dev3'
14
14
  '''The full version of MPF.'''
15
15
 
16
16
  __short_version__ = '0.81'
mpf/config_spec.yaml CHANGED
@@ -544,7 +544,7 @@ fast:
544
544
  __type__: config
545
545
  net: single|subconfig(fast_net)|None
546
546
  exp: single|subconfig(fast_exp)|None
547
- exp_int: single|subconfig(fast_exp)|None
547
+ exp_int: single|subconfig(fast_exp)|None
548
548
  aud: single|subconfig(fast_aud)|None
549
549
  seg: single|subconfig(fast_seg)|None
550
550
  dmd: single|subconfig(fast_dmd)|None
@@ -834,7 +834,7 @@ neoseg_displays:
834
834
  number_template: single|str|None
835
835
  start_x: single|float|None
836
836
  start_y: single|float|None
837
- size: single|enum(2digit,8digit)|8digit
837
+ size: single|enum(2digit,8digit,2digit-rg-swapped,8digit-rg-swapped)|8digit
838
838
  light_template: single|subconfig(lights,device)|
839
839
  lights:
840
840
  __valid_in__: machine
@@ -1414,7 +1414,7 @@ settings:
1414
1414
  label: single|str|
1415
1415
  sort: single|int|
1416
1416
  values: dict|str:str|
1417
- key_type: single|enum(str,float,int)|str
1417
+ key_type: single|enum(str,float,int,bool)|str
1418
1418
  default: single|str|
1419
1419
  machine_var: single|str|None
1420
1420
  settingType: single|str|standard
@@ -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
- await asyncio.wait([asyncio.create_task(futures) for futures in futures])
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/events.py CHANGED
@@ -224,14 +224,15 @@ class EventManager(MpfController):
224
224
  'handler you passed?'.format(handler, event))
225
225
 
226
226
  sig = inspect.signature(handler)
227
- if 'kwargs' not in sig.parameters:
228
- raise AssertionError("Handler {} for event '{}' is missing **kwargs. Actual signature: {}".format(
229
- handler, event, sig))
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):
@@ -4,7 +4,7 @@ import string
4
4
  import asyncio
5
5
  import operator as op
6
6
  import abc
7
- from functools import lru_cache
7
+ from functools import lru_cache, partial
8
8
 
9
9
  import re
10
10
  from typing import Tuple, List, Any, Union
@@ -860,8 +860,28 @@ class BasePlaceholderManager(MpfController):
860
860
  subscriptions.append(self.machine.wait_for_stop())
861
861
  future = Util.any(subscriptions)
862
862
  future = asyncio.ensure_future(future)
863
+
864
+ future.add_done_callback(
865
+ partial(self._cleanup_placeholder_subscriptions, subscriptions=subscriptions)
866
+ )
863
867
  return value, future
864
868
 
869
+ def _cleanup_placeholder_subscriptions(self, future, subscriptions):
870
+ """Cancel child placeholder subscriptions when the parent template frame finishes."""
871
+ del future
872
+
873
+ for sub in subscriptions:
874
+ if isinstance(sub, asyncio.Future):
875
+ if not sub.done():
876
+ # Cancel outstanding asyncio Future/Task instance
877
+ sub.cancel()
878
+ elif asyncio.iscoroutine(sub):
879
+ try:
880
+ # close raw coroutines (like sleep)
881
+ sub.close()
882
+ except RuntimeWarning:
883
+ pass
884
+
865
885
  @lru_cache(typed=True)
866
886
  def parse_conditional_template(self, template, default_number=None):
867
887
  """Parse a template for condition and number and return a dict."""
@@ -57,6 +57,8 @@ class Util:
57
57
  return float(value)
58
58
  if type_name == "str":
59
59
  return str(value)
60
+ if type_name == "bool":
61
+ return str(value).lower() in ['true', 't', 'yes', 'enable', 'on', '1']
60
62
 
61
63
  raise AssertionError("Unknown type {}".format(type_name))
62
64
 
@@ -586,13 +588,16 @@ class Util:
586
588
  if isinstance(time_string, (int, float)):
587
589
  return int(time_string)
588
590
 
589
- time_string = str(time_string).upper()
591
+ time_string = str(time_string).upper().strip()
590
592
 
591
- if time_string.endswith('MS') or time_string.endswith('MSEC'):
592
- return int(time_string[:-2])
593
+ if time_string == 'NONE':
594
+ return 0
595
+
596
+ if time_string.endswith('MS'):
597
+ return int(float(time_string[:-2]))
593
598
 
594
599
  if time_string.endswith('MSEC'):
595
- return int(time_string[:-4])
600
+ return int(float(time_string[:-4]))
596
601
 
597
602
  if time_string.endswith('D'):
598
603
  return int(float(time_string[:-1]) * 86400 * 1000)
@@ -609,7 +614,7 @@ class Util:
609
614
  if time_string.endswith('SEC'):
610
615
  return int(float(time_string[:-3]) * 1000)
611
616
 
612
- return int(time_string)
617
+ return int(float(time_string))
613
618
 
614
619
  @staticmethod
615
620
  def string_to_secs(time_string: str) -> float:
@@ -847,4 +852,4 @@ class Util:
847
852
  bool: True if the bit is set, False otherwise
848
853
  """
849
854
  num = int(hex_string, 16)
850
- return bool(num & (1 << bit))
855
+ return bool(num & (1 << bit))
@@ -14,6 +14,21 @@ from mpf.core.system_wide_device import SystemWideDevice
14
14
  from mpf.devices.light import Light
15
15
 
16
16
 
17
+ NEOSEG_8DIGIT_ORDER = [
18
+ 95, 90, 93, 82, 85, 89, 86, 91, 88, 87, 81, 92, 83, 84, 94,
19
+ 104, 76, 79, 96, 99, 103, 100, 77, 102, 101, 75, 78, 97, 98, 80,
20
+ 110, 105, 108, 67, 70, 74, 71, 106, 73, 72, 66, 107, 68, 69, 109,
21
+ 119, 61, 64, 111, 114, 118, 115, 62, 117, 116, 60, 63, 112, 113, 65,
22
+ 5, 0, 3, 52, 55, 59, 56, 1, 58, 57, 51, 2, 53, 54, 4,
23
+ 14, 46, 49, 6, 9, 13, 10, 47, 12, 11, 45, 48, 7, 8, 50,
24
+ 20, 15, 18, 37, 40, 44, 41, 16, 43, 42, 36, 17, 38, 39, 19,
25
+ 29, 31, 34, 21, 24, 28, 25, 32, 27, 26, 30, 33, 22, 23, 35]
26
+
27
+ NEOSEG_2DIGIT_ORDER = [
28
+ 5, 0, 3, 22, 25, 29, 26, 1, 28, 27, 21, 2, 23, 24, 4,
29
+ 14, 16, 19, 6, 9, 13, 10, 17, 12, 11, 15, 18, 7, 8, 20]
30
+
31
+
17
32
  class LightGroup(SystemWideDevice):
18
33
 
19
34
  """An abstract group of lights."""
@@ -92,25 +107,41 @@ class LightGroup(SystemWideDevice):
92
107
  for light in self.lights:
93
108
  light.color(color, fade_ms, priority, key)
94
109
 
110
+ def _swap_grb_rgb_channel_offsets(self, offsets):
111
+ """Swap every first and second out of sets of three (GRB->RGB)."""
112
+ adjustments = {0: 1, 1: -1, 2: 0}
113
+ return [
114
+ x + adjustments[x % 3]
115
+ for x in offsets
116
+ ]
117
+
95
118
  def _reorder_lights(self):
96
119
  if self.config['size'] == '8digit':
97
120
  #Magic order for 8 digit NeoSeg displays from CobraPin
98
- order = [95, 90, 93, 82, 85, 89, 86, 91, 88, 87, 81, 92, 83, 84, 94,
99
- 104, 76, 79, 96, 99, 103, 100, 77, 102, 101, 75, 78, 97, 98, 80,
100
- 110, 105, 108, 67, 70, 74, 71, 106, 73, 72, 66, 107, 68, 69, 109,
101
- 119, 61, 64, 111, 114, 118, 115, 62, 117, 116, 60, 63, 112, 113, 65,
102
- 5, 0, 3, 52, 55, 59, 56, 1, 58, 57, 51, 2, 53, 54, 4,
103
- 14, 46, 49, 6, 9, 13, 10, 47, 12, 11, 45, 48, 7, 8, 50,
104
- 20, 15, 18, 37, 40, 44, 41, 16, 43, 42, 36, 17, 38, 39, 19,
105
- 29, 31, 34, 21, 24, 28, 25, 32, 27, 26, 30, 33, 22, 23, 35]
121
+ order = NEOSEG_8DIGIT_ORDER
122
+
106
123
  elif self.config['size'] == '2digit':
107
124
  #Magic order for 2 digit NeoSeg displays from CobraPin
108
- order = [5, 0, 3, 22, 25, 29, 26, 1, 28, 27, 21, 2, 23, 24, 4,
109
- 14, 16, 19, 6, 9, 13, 10, 17, 12, 11, 15, 18, 7, 8, 20]
125
+ order = NEOSEG_2DIGIT_ORDER
126
+
127
+ elif self.config['size'] == '8digit-rg-swapped':
128
+ #Magic order for 8 digit NeoSeg displays from CobraPin when platform numbering uses RGB channel order
129
+ order = self._swap_grb_rgb_channel_offsets(NEOSEG_8DIGIT_ORDER)
130
+
131
+ elif self.config['size'] == '2digit-rg-swapped':
132
+ #Magic order for 2 digit NeoSeg displays from CobraPin when platform numbering uses RGB channel order
133
+ order = self._swap_grb_rgb_channel_offsets(NEOSEG_2DIGIT_ORDER)
110
134
  else:
111
135
  order = range(0, len(self.lights))
112
136
 
113
- self.lights = [self.lights[i] for i in order]
137
+ mapped_lights = []
138
+ try:
139
+ for i in order:
140
+ mapped_lights.append(self.lights[i])
141
+ self.lights = mapped_lights
142
+ except IndexError:
143
+ self.error_log("Attempted to use light index %s but source list only had %s items", i, len(self.lights))
144
+ raise
114
145
 
115
146
  def wait_for_loaded(self):
116
147
  """Return future."""
@@ -180,11 +211,12 @@ class NeoSegDisplay(LightGroup):
180
211
  __slots__ = [] # type: List[str]
181
212
 
182
213
  def _create_lights(self):
183
- if self.config['size'] == '8digit':
214
+ if self.config['size'] in ('8digit', '8digit-rg-swapped'):
184
215
  count = 120
185
- elif self.config['size'] == '2digit':
216
+ elif self.config['size'] in ('2digit', '2digit-rg-swapped'):
186
217
  count = 30
187
218
  else:
219
+ self.warning_log("NeoSegDisplay %s could not determine number of lights to create.", self.name)
188
220
  count = 0
189
221
 
190
222
  for index in range(self.config['number_start'], self.config['number_start'] + count):
@@ -593,10 +593,13 @@ class FastHardwarePlatform(ServoPlatform, LightsPlatform, RgbDmdPlatform,
593
593
  return FASTMatrixLight(number, self.serial_connections['net'], self.machine,
594
594
  int(1 / self.config['net']['lamp_hz'] * 1000), self)
595
595
  if not subtype or subtype == "led":
596
- # make everything lowercase and strip trailing channel number
597
- parts, channel = number.lower().rsplit('-', 1)
598
- # split into board name, breakout, port, led
599
- parts = parts.split('-')
596
+ try:
597
+ # make everything lowercase and strip trailing channel number
598
+ parts, channel = number.lower().rsplit('-', 1)
599
+ # split into board name, breakout, port, led
600
+ parts = parts.split('-')
601
+ except ValueError as e:
602
+ raise ValueError(f"Unable to parse FAST light '{config.name}' address {number}") from e
600
603
 
601
604
  if parts[0] in self.exp_boards_by_name: # this is an expansion board LED in config file format
602
605
  return self._add_exp_led_with_config_format(parts, channel, config.name)
@@ -40,7 +40,11 @@ class FASTRGBLED:
40
40
  channel = self.channels[index]
41
41
  if channel:
42
42
  brightness, _, done = channel.get_fade_and_brightness(current_time)
43
- result += f'{int(brightness * 255):02X}'
43
+ # Clamp to a single byte: an out-of-range brightness here would
44
+ # format to a 1- or 3-char hex channel and crash the downstream
45
+ # binascii unpacking with an "Odd-length string" error.
46
+ clamped_brightness = min(255, max(0, int(brightness * 255)))
47
+ result += f'{clamped_brightness:02X}'
44
48
  if not done:
45
49
  self.dirty = True
46
50
  else:
@@ -116,6 +120,12 @@ class FASTLEDChannel(LightPlatformInterface):
116
120
  fade_ms = max_fade_ms
117
121
  ratio = ((current_time + (fade_ms / 1000.0) - start_time) /
118
122
  (target_time - start_time))
123
+ # A fade's start_time and the current_time passed in come from
124
+ # separate clock reads, so current_time can land a hair before
125
+ # start_time, making this ratio slightly out of [0, 1]. That would
126
+ # push brightness below 0 (fade up) or above 1 (fade down); clamp
127
+ # the ratio so the interpolation stays in range for both directions.
128
+ ratio = max(0.0, min(1.0, ratio))
119
129
  brightness = start_brightness + (target_brightness - start_brightness) * ratio
120
130
  done = False
121
131
  else:
@@ -125,15 +135,15 @@ class FASTLEDChannel(LightPlatformInterface):
125
135
  self._last_brightness = brightness
126
136
  done = True
127
137
 
128
- # There is a bug that can sometimes cause the start_time to be ahead of the current_time,
129
- # resulting in a negative brightness value. This may be a floating-point rounding error,
130
- # or maybe something else. I can't figure it out, so just floor the value to be non-negative.
131
- if brightness < 0:
132
- brightness = 0
133
- self.led.log.warning("Calculated a negative brightness (%s) for led %s channel %s. current_time: %s "
138
+ # Final guard in case start/target brightness themselves are ever out
139
+ # of [0, 1]: an out-of-range value would crash the hex formatter in
140
+ # current_color, so clamp both ends.
141
+ if brightness < 0 or brightness > 1:
142
+ self.led.log.warning("Calculated an out-of-range brightness (%s) for led %s channel %s. current_time: %s "
134
143
  "start_brightness: %s, start_time: %s, target_brightness: %s, target_time: %s",
135
144
  brightness, self.led, self.channel, current_time, start_brightness, start_time,
136
145
  target_brightness, target_time)
146
+ brightness = max(0.0, min(1.0, brightness))
137
147
 
138
148
  return brightness, fade_ms, done
139
149
 
@@ -160,7 +160,7 @@ class VirtualPinballPlatform(LightsPlatform, SwitchPlatform, DriverPlatform, Seg
160
160
 
161
161
  """VPX platform."""
162
162
 
163
- __slots__ = ["_lights", "_switches", "_drivers", "_last_drivers", "_last_lights",
163
+ __slots__ = ["_lights", "_switches", "_drivers", "_last_drivers", "_last_lights",
164
164
  "_started", "rules", "_configured_segment_displays", "_last_segment_text"]
165
165
 
166
166
  def __init__(self, machine):
@@ -6,7 +6,7 @@ game:
6
6
  player_vars:
7
7
  score:
8
8
  initial_value: 0
9
- my_var:
9
+ my_var:
10
10
  initial_value: 0
11
11
 
12
12
  auditor:
@@ -6,7 +6,11 @@ class TestCustomCode(CustomCode):
6
6
  self.log.debug("Loaded!")
7
7
 
8
8
  self.machine.events.add_handler('test_event', self._update)
9
+ self.machine.events.add_handler('test_event_2', self._alternative_kwargs)
9
10
 
10
11
  def _update(self, **kwargs):
11
12
  del kwargs
12
13
  self.machine.events.post("test_response")
14
+
15
+ def _alternative_kwargs(self, **_kwargs):
16
+ self.machine.events.post("test_response_2")
@@ -7,6 +7,12 @@ event_player:
7
7
  test_event_player_delayed:
8
8
  - test_event_player2|2s
9
9
  - test_event_player3:2s
10
+ "{machine.lifetime_earnings >= 500}": never_happens_machine
11
+ "{machine.switches.s_test_1.state == 1}": sometimes_happens_switch
12
+
13
+ switches:
14
+ s_test_1:
15
+ number:
10
16
 
11
17
  random_event_player:
12
18
  test_random_event_player1:
@@ -3,6 +3,11 @@ mode:
3
3
  start_events: game_mode_start
4
4
  stop_events: game_mode_end
5
5
 
6
+ event_player:
7
+ "{players[0].score>=99999999}": never_happens_players
8
+ "{current_player.score>=999999999}": never_happens_player
9
+ "{current_player.score<=current_player.score + 1 and current_player.score > -1}": always_happens_multiple_ast
10
+
6
11
  random_event_player:
7
12
  test_random_event_player_mode2:
8
13
  - out1
@@ -50,3 +50,15 @@ neoseg_displays:
50
50
  light_template:
51
51
  type: w
52
52
  subtype: led
53
+ neoSeg_2:
54
+ start_channel: 0-1-0
55
+ size: 8digit-rg-swapped
56
+ light_template:
57
+ type: w
58
+ subtype: led
59
+ neoSeg_3:
60
+ start_channel: 0-1-120
61
+ size: 2digit-rg-swapped
62
+ light_template:
63
+ type: w
64
+ subtype: led
@@ -67,7 +67,7 @@ lights:
67
67
  # tracks:
68
68
  # sfx: []
69
69
  # enabled: true
70
- #
70
+ #
71
71
  # keyboard:
72
72
  # right:
73
73
  # switch: s_service_enter
@@ -19,3 +19,30 @@ settings:
19
19
  zero: "Zero"
20
20
  one: "One"
21
21
  two: "Two"
22
+ custom_setting_float:
23
+ label: "Float Setting"
24
+ key_type: float
25
+ default: 0.1
26
+ sort: 3
27
+ values:
28
+ 0: "Zero"
29
+ 0.1: "Point One"
30
+ 1.0: "One"
31
+ 2.5: "Two Point Five"
32
+ custom_setting_bool_t:
33
+ label: "Boolean Setting Default True"
34
+ key_type: bool
35
+ default: true
36
+ sort: 4
37
+ values:
38
+ true: "yes"
39
+ false: "off"
40
+
41
+ custom_setting_bool_f:
42
+ label: "Boolean Setting Default False"
43
+ key_type: bool
44
+ default: false
45
+ sort: 5
46
+ values:
47
+ true: "on"
48
+ false: "no"
@@ -15,3 +15,10 @@ class TestCustomCode(MpfTestCase):
15
15
  self.machine_run()
16
16
 
17
17
  self.assertEqual(1, self._events['test_response'])
18
+
19
+ def test_scoring_2(self):
20
+ self.mock_event("test_response_2")
21
+ self.post_event("test_event_2")
22
+ self.machine_run()
23
+
24
+ self.assertEqual(1, self._events['test_response_2'])
@@ -1,5 +1,8 @@
1
+ import asyncio
1
2
  import inspect
2
3
 
4
+ from unittest.mock import patch, AsyncMock, MagicMock
5
+
3
6
  from mpf.core.utility_functions import Util
4
7
  from mpf.tests.MpfTestCase import MpfTestCase
5
8
 
@@ -37,3 +40,63 @@ class TestDeviceManager(MpfTestCase):
37
40
  self.assertTrue(hasattr(method, "relative_priority"),
38
41
  "Method {}.{} is missing a relative_priority. Did you apply the event_handler "
39
42
  "decorator?".format(device_type, method_name))
43
+
44
+ def test_initialize_devices_re_raises_first_exception(self):
45
+ """Test that initialize_devices re-raises exceptions from failed device initialization."""
46
+ async def test_exception_handling():
47
+ failing_task = asyncio.create_task(self._failing_coro())
48
+ slow_task = asyncio.create_task(self._slow_coro())
49
+
50
+ fs = [failing_task, slow_task]
51
+ done, pending = await asyncio.wait(fs, return_when=asyncio.FIRST_EXCEPTION)
52
+
53
+ for task in done:
54
+ if task.cancelled():
55
+ continue
56
+ exc = task.exception()
57
+ if exc is not None:
58
+ for pending_task in pending:
59
+ pending_task.cancel()
60
+ await asyncio.gather(*pending, return_exceptions=True)
61
+ raise exc
62
+
63
+ with self.assertRaises(RuntimeError):
64
+ self.loop.run_until_complete(test_exception_handling())
65
+
66
+ def test_initialize_devices_completes_when_all_succeed(self):
67
+ """Test that initialize_devices completes normally when all devices succeed."""
68
+ async def test_success_handling():
69
+ success1 = asyncio.create_task(self._success_coro())
70
+ success2 = asyncio.create_task(self._success_coro())
71
+
72
+ fs = [success1, success2]
73
+ done, pending = await asyncio.wait(fs, return_when=asyncio.FIRST_EXCEPTION)
74
+
75
+ for task in done:
76
+ if task.cancelled():
77
+ continue
78
+ exc = task.exception()
79
+ if exc is not None:
80
+ for pending_task in pending:
81
+ pending_task.cancel()
82
+ await asyncio.gather(*pending, return_exceptions=True)
83
+ raise exc
84
+
85
+ return "success"
86
+
87
+ result = self.loop.run_until_complete(test_success_handling())
88
+ self.assertEqual(result, "success")
89
+
90
+ async def _failing_coro(self):
91
+ """Async coroutine that fails."""
92
+ raise RuntimeError("Device initialization failed")
93
+
94
+ async def _slow_coro(self):
95
+ """Async coroutine that takes time."""
96
+ await asyncio.sleep(10)
97
+ return "done"
98
+
99
+ async def _success_coro(self):
100
+ """Async coroutine that succeeds."""
101
+ await asyncio.sleep(0)
102
+ return "success"
@@ -912,3 +912,48 @@ class TestEventManager(MpfFakeGameTestCase, MpfTestCase):
912
912
  # invalid space
913
913
  with self.assertRaises(ValueError):
914
914
  self.machine.events.add_handler("event_name {machine.variables.test}", self._handler)
915
+
916
+ def test_handler_cleanup_on_variable_change(self):
917
+ self.start_game()
918
+ self.machine.events.post('game_mode_start')
919
+ self.advance_time_and_run(0.01)
920
+
921
+ initial_count_pte = len(self.machine.events.registered_handlers['player_turn_ended'])
922
+ initial_count_player_added = len(self.machine.events.registered_handlers['player_added'])
923
+
924
+ for i in range(100):
925
+ self.machine.game.player.score += 100
926
+ self.advance_time_and_run(0.01)
927
+
928
+ final_count_pte = len(self.machine.events.registered_handlers['player_turn_ended'])
929
+ final_count_player_added = len(self.machine.events.registered_handlers['player_added'])
930
+ self.assertEqual(initial_count_pte, final_count_pte)
931
+ self.assertEqual(initial_count_player_added, final_count_player_added)
932
+
933
+ def test_handler_cleanup_on_machine_variable_change(self):
934
+ self.start_game()
935
+ self.advance_time_and_run(0.01)
936
+
937
+ event_name = 'machine_var_lifetime_earnings'
938
+ initial_count = len(self.machine.events.registered_handlers.get(event_name, []))
939
+
940
+ for i in range(100):
941
+ self.machine.variables.set_machine_var('lifetime_earnings', 10 + i)
942
+ self.advance_time_and_run(0.01)
943
+
944
+ final_count = len(self.machine.events.registered_handlers.get(event_name, []))
945
+ self.assertEqual(initial_count, final_count)
946
+
947
+ def test_handler_cleanup_on_switch_state_change(self):
948
+ self.start_game()
949
+ self.advance_time_and_run(0.01)
950
+
951
+ event_name = 'switch_s_test_1_active'
952
+ initial_count = len(self.machine.events.registered_handlers.get(event_name, []))
953
+
954
+ for _ in range(50):
955
+ self.hit_switch_and_run('s_test_1', 0.01)
956
+ self.release_switch_and_run('s_test_1', 0.01)
957
+
958
+ final_count = len(self.machine.events.registered_handlers.get(event_name, []))
959
+ self.assertEqual(initial_count, final_count)
@@ -0,0 +1,61 @@
1
+ # mpf.tests.test_Fast_Led
2
+ """Unit tests for FAST LED fade/brightness math."""
3
+ import unittest
4
+ from unittest.mock import MagicMock
5
+
6
+ from mpf.platforms.fast.fast_led import FASTLEDChannel
7
+
8
+
9
+ class _FakeLed:
10
+ """Minimal stand-in for FASTRGBLED for unit-testing a single channel."""
11
+
12
+ def __init__(self, hardware_fade_ms=0):
13
+ self.number = '880'
14
+ self.hardware_fade_ms = hardware_fade_ms
15
+ self.dirty = False
16
+ self.log = MagicMock()
17
+
18
+
19
+ class TestFastLed(unittest.TestCase):
20
+
21
+ def _channel(self, hardware_fade_ms=0):
22
+ return FASTLEDChannel(_FakeLed(hardware_fade_ms), 0)
23
+
24
+ def test_fade_interpolates_to_three_places(self):
25
+ # Half-way through a 0 -> 1 fade the brightness is 0.5.
26
+ ch = self._channel()
27
+ start = 1000.0
28
+ ch.set_fade(start_brightness=0.0, start_time=start,
29
+ target_brightness=1.0, target_time=start + 1.0)
30
+
31
+ brightness, _, _ = ch.get_fade_and_brightness(start + 0.5)
32
+
33
+ self.assertAlmostEqual(brightness, 0.5, places=3)
34
+
35
+ def test_fade_up_when_current_time_precedes_start_time(self):
36
+ # When current_time reads as before the fade's start_time, a fade up
37
+ # must not calculate a brightness below 0.
38
+ ch = self._channel()
39
+ now = 1000.0
40
+ ch.set_fade(start_brightness=0.0, start_time=now + 0.01,
41
+ target_brightness=1.0, target_time=now + 0.5)
42
+
43
+ brightness, _, _ = ch.get_fade_and_brightness(now)
44
+
45
+ self.assertGreaterEqual(brightness, 0.0)
46
+ self.assertLessEqual(brightness, 1.0)
47
+
48
+ def test_fade_down_when_current_time_precedes_start_time(self):
49
+ # The same out-of-order read while fading down must not calculate a
50
+ # brightness above 1, which would format to a 3-char hex channel and
51
+ # crash the downstream binascii unpacking.
52
+ ch = self._channel()
53
+ now = 1000.0
54
+ ch.set_fade(start_brightness=1.0, start_time=now + 0.01,
55
+ target_brightness=0.0, target_time=now + 0.5)
56
+
57
+ brightness, _, _ = ch.get_fade_and_brightness(now)
58
+
59
+ self.assertGreaterEqual(brightness, 0.0)
60
+ self.assertLessEqual(brightness, 1.0)
61
+ self.assertEqual(2, len(f'{int(brightness * 255):02X}'))
@@ -20,8 +20,7 @@ class TestLightGroups(MpfTestCase):
20
20
  self.assertLightColor("stripe1_light_3", "red")
21
21
  self.assertLightColor("stripe1_light_4", "red")
22
22
 
23
- def test_config(self):
24
- # stripe 1
23
+ def test_config_stripe_1(self):
25
24
  self.assertEqual("led-10-r", self.machine.lights["stripe1_light_0"].hw_drivers["red"][0].number)
26
25
  self.assertListEqual(["test", "stripe1"], self.machine.lights["stripe1_light_0"].config['tags'])
27
26
  self.assertEqual("led-11-r", self.machine.lights["stripe1_light_1"].hw_drivers["red"][0].number)
@@ -30,7 +29,7 @@ class TestLightGroups(MpfTestCase):
30
29
  self.assertEqual("led-14-r", self.machine.lights["stripe1_light_4"].hw_drivers["red"][0].number)
31
30
  self.assertListEqual(["test", "stripe1"], self.machine.lights["stripe1_light_4"].config['tags'])
32
31
 
33
- # stripe 2
32
+ def test_config_stripe_2(self):
34
33
  self.assertEqual("led-7-200-r", self.machine.lights["stripe2_light_0"].hw_drivers["red"][0].number)
35
34
  self.assertEqual(10, self.machine.lights["stripe2_light_0"].config['x'])
36
35
  self.assertEqual(20, self.machine.lights["stripe2_light_0"].config['y'])
@@ -38,17 +37,14 @@ class TestLightGroups(MpfTestCase):
38
37
  self.assertEqual(15, self.machine.lights["stripe2_light_1"].config['x'])
39
38
  self.assertEqual(20, self.machine.lights["stripe2_light_1"].config['y'])
40
39
 
41
- # stripe 3
40
+ def test_config_stripe_3(self):
42
41
  self.assertEqual("led-ABC-123", self.machine.lights["stripe3_light_0"].hw_drivers["red"][0].number)
43
42
  self.assertEqual("led-ABC-123+1", self.machine.lights["stripe3_light_0"].hw_drivers["green"][0].number)
44
- self.assertEqual("led-ABC-123+2",
45
- self.machine.lights["stripe3_light_0"].hw_drivers["blue"][0].number)
46
- self.assertEqual("led-ABC-123+3",
47
- self.machine.lights["stripe3_light_0"].hw_drivers["white"][0].number)
48
- self.assertEqual("led-ABC-123+4",
49
- self.machine.lights["stripe3_light_1"].hw_drivers["red"][0].number)
50
-
51
- # ring 1
43
+ self.assertEqual("led-ABC-123+2", self.machine.lights["stripe3_light_0"].hw_drivers["blue"][0].number)
44
+ self.assertEqual("led-ABC-123+3", self.machine.lights["stripe3_light_0"].hw_drivers["white"][0].number)
45
+ self.assertEqual("led-ABC-123+4", self.machine.lights["stripe3_light_1"].hw_drivers["red"][0].number)
46
+
47
+ def test_config_rings(self):
52
48
  self.assertEqual("led-20-r", self.machine.lights["ring1_light_0"].hw_drivers["red"][0].number)
53
49
  self.assertEqual("led-21-r", self.machine.lights["ring1_light_1"].hw_drivers["red"][0].number)
54
50
  self.assertEqual("led-22-r", self.machine.lights["ring1_light_2"].hw_drivers["red"][0].number)
@@ -67,13 +63,18 @@ class TestLightGroups(MpfTestCase):
67
63
  self.assertEqual(100, self.machine.lights["ring1_light_9"].config['x'])
68
64
  self.assertEqual(53, self.machine.lights["ring1_light_9"].config['y'])
69
65
 
70
- # neoSeg_0
66
+ def test_config_neoSeg_0(self): # 8 digit
71
67
  self.assertEqual("led-0-0-0", self.machine.lights["neoSeg_0_light_0"].hw_drivers["white"][0].number)
72
68
  self.assertEqual("led-0-0-0+1", self.machine.lights["neoSeg_0_light_1"].hw_drivers["white"][0].number)
73
69
  self.assertEqual("led-0-0-0+2", self.machine.lights["neoSeg_0_light_2"].hw_drivers["white"][0].number)
74
70
  self.assertEqual("neoSeg_0_light_119", self.machine.lights["neoSeg_0_light_119"].name)
75
- # sanity check order...not 100%
71
+
76
72
  self.assertEqual(self.machine.neoseg_displays["neoSeg_0"].lights[0].name, self.machine.lights["neoSeg_0_light_95"].name)
73
+ self.assertEqual(self.machine.neoseg_displays["neoSeg_0"].lights[1].name, self.machine.lights["neoSeg_0_light_90"].name)
74
+ self.assertEqual(self.machine.neoseg_displays["neoSeg_0"].lights[2].name, self.machine.lights["neoSeg_0_light_93"].name)
75
+ self.assertEqual(self.machine.neoseg_displays["neoSeg_0"].lights[3].name, self.machine.lights["neoSeg_0_light_82"].name)
76
+ self.assertEqual(self.machine.neoseg_displays["neoSeg_0"].lights[4].name, self.machine.lights["neoSeg_0_light_85"].name)
77
+ self.assertEqual(self.machine.neoseg_displays["neoSeg_0"].lights[5].name, self.machine.lights["neoSeg_0_light_89"].name)
77
78
  self.assertEqual(self.machine.neoseg_displays["neoSeg_0"].lights[20].name, self.machine.lights["neoSeg_0_light_103"].name)
78
79
  self.assertEqual(self.machine.neoseg_displays["neoSeg_0"].lights[40].name, self.machine.lights["neoSeg_0_light_66"].name)
79
80
  self.assertEqual(self.machine.neoseg_displays["neoSeg_0"].lights[60].name, self.machine.lights["neoSeg_0_light_5"].name)
@@ -81,12 +82,12 @@ class TestLightGroups(MpfTestCase):
81
82
  self.assertEqual(self.machine.neoseg_displays["neoSeg_0"].lights[100].name, self.machine.lights["neoSeg_0_light_36"].name)
82
83
  self.assertEqual(self.machine.neoseg_displays["neoSeg_0"].lights[119].name, self.machine.lights["neoSeg_0_light_35"].name)
83
84
 
84
- # neoSeg_1
85
+ def test_config_neoSeg_1(self): # 2 digit
85
86
  self.assertEqual("led-0-0-120", self.machine.lights["neoSeg_1_light_0"].hw_drivers["white"][0].number)
86
87
  self.assertEqual("led-0-0-120+1", self.machine.lights["neoSeg_1_light_1"].hw_drivers["white"][0].number)
87
88
  self.assertEqual("led-0-0-120+2", self.machine.lights["neoSeg_1_light_2"].hw_drivers["white"][0].number)
88
89
  self.assertEqual("neoSeg_1_light_29", self.machine.lights["neoSeg_1_light_29"].name)
89
- # sanity check order...not 100%
90
+
90
91
  self.assertEqual(self.machine.neoseg_displays["neoSeg_1"].lights[0].name, self.machine.lights["neoSeg_1_light_5"].name)
91
92
  self.assertEqual(self.machine.neoseg_displays["neoSeg_1"].lights[5].name, self.machine.lights["neoSeg_1_light_29"].name)
92
93
  self.assertEqual(self.machine.neoseg_displays["neoSeg_1"].lights[10].name, self.machine.lights["neoSeg_1_light_21"].name)
@@ -94,3 +95,34 @@ class TestLightGroups(MpfTestCase):
94
95
  self.assertEqual(self.machine.neoseg_displays["neoSeg_1"].lights[20].name, self.machine.lights["neoSeg_1_light_13"].name)
95
96
  self.assertEqual(self.machine.neoseg_displays["neoSeg_1"].lights[25].name, self.machine.lights["neoSeg_1_light_15"].name)
96
97
  self.assertEqual(self.machine.neoseg_displays["neoSeg_1"].lights[29].name, self.machine.lights["neoSeg_1_light_20"].name)
98
+
99
+ def test_config_neoSeg_2(self): # 8 digit on rgb-ordered channels
100
+ self.assertEqual("led-0-1-0", self.machine.lights["neoSeg_2_light_0"].hw_drivers["white"][0].number)
101
+ self.assertEqual("led-0-1-0+1", self.machine.lights["neoSeg_2_light_1"].hw_drivers["white"][0].number)
102
+ self.assertEqual("neoSeg_2_light_119", self.machine.lights["neoSeg_2_light_119"].name)
103
+
104
+ self.assertEqual(self.machine.neoseg_displays["neoSeg_2"].lights[0].name, self.machine.lights["neoSeg_2_light_95"].name)
105
+ self.assertEqual(self.machine.neoseg_displays["neoSeg_2"].lights[1].name, self.machine.lights["neoSeg_2_light_91"].name)
106
+ self.assertEqual(self.machine.neoseg_displays["neoSeg_2"].lights[2].name, self.machine.lights["neoSeg_2_light_94"].name)
107
+ self.assertEqual(self.machine.neoseg_displays["neoSeg_2"].lights[3].name, self.machine.lights["neoSeg_2_light_81"].name)
108
+ self.assertEqual(self.machine.neoseg_displays["neoSeg_2"].lights[4].name, self.machine.lights["neoSeg_2_light_84"].name)
109
+ self.assertEqual(self.machine.neoseg_displays["neoSeg_2"].lights[5].name, self.machine.lights["neoSeg_2_light_89"].name)
110
+ self.assertEqual(self.machine.neoseg_displays["neoSeg_2"].lights[20].name, self.machine.lights["neoSeg_2_light_102"].name)
111
+ self.assertEqual(self.machine.neoseg_displays["neoSeg_2"].lights[40].name, self.machine.lights["neoSeg_2_light_67"].name)
112
+ self.assertEqual(self.machine.neoseg_displays["neoSeg_2"].lights[60].name, self.machine.lights["neoSeg_2_light_5"].name)
113
+ self.assertEqual(self.machine.neoseg_displays["neoSeg_2"].lights[80].name, self.machine.lights["neoSeg_2_light_12"].name)
114
+ self.assertEqual(self.machine.neoseg_displays["neoSeg_2"].lights[100].name, self.machine.lights["neoSeg_2_light_37"].name)
115
+ self.assertEqual(self.machine.neoseg_displays["neoSeg_2"].lights[119].name, self.machine.lights["neoSeg_2_light_35"].name)
116
+
117
+ def test_config_neoSeg_3(self): # 2 digit on rgb-ordered channels
118
+ self.assertEqual("led-0-1-120", self.machine.lights["neoSeg_3_light_0"].hw_drivers["white"][0].number)
119
+ self.assertEqual("led-0-1-120+1", self.machine.lights["neoSeg_3_light_1"].hw_drivers["white"][0].number)
120
+ self.assertEqual("neoSeg_3_light_29", self.machine.lights["neoSeg_3_light_29"].name)
121
+
122
+ self.assertEqual(self.machine.neoseg_displays["neoSeg_3"].lights[0].name, self.machine.lights["neoSeg_3_light_5"].name)
123
+ self.assertEqual(self.machine.neoseg_displays["neoSeg_3"].lights[5].name, self.machine.lights["neoSeg_3_light_29"].name)
124
+ self.assertEqual(self.machine.neoseg_displays["neoSeg_3"].lights[10].name, self.machine.lights["neoSeg_3_light_22"].name)
125
+ self.assertEqual(self.machine.neoseg_displays["neoSeg_3"].lights[15].name, self.machine.lights["neoSeg_3_light_14"].name)
126
+ self.assertEqual(self.machine.neoseg_displays["neoSeg_3"].lights[20].name, self.machine.lights["neoSeg_3_light_12"].name)
127
+ self.assertEqual(self.machine.neoseg_displays["neoSeg_3"].lights[25].name, self.machine.lights["neoSeg_3_light_16"].name)
128
+ self.assertEqual(self.machine.neoseg_displays["neoSeg_3"].lights[29].name, self.machine.lights["neoSeg_3_light_20"].name)
@@ -14,8 +14,7 @@ class TestLightNumbering(MpfTestCase):
14
14
  led1 = self.machine.lights["first"]
15
15
  led2 = self.machine.lights["second"]
16
16
  led3 = self.machine.lights["third"]
17
- led4 = self.machine.lights["fourth"]
18
-
17
+ led4 = self.machine.lights["fourth"]
19
18
 
20
19
  self.assertEqual(led1.hw_drivers["green"][0].number,"led-0-0-0")
21
20
  self.assertEqual(led1.hw_drivers["red"][0].number,"led-0-0-0+1")
@@ -35,4 +34,4 @@ class TestLightNumbering(MpfTestCase):
35
34
  self.assertEqual(led4.hw_drivers["green"][0].number,"led-0-0-0+12")
36
35
  self.assertEqual(led4.hw_drivers["red"][0].number,"led-0-0-0+13")
37
36
  self.assertEqual(led4.hw_drivers["blue"][0].number,"led-0-0-0+14")
38
- self.assertEqual(led4.hw_drivers["white"][0].number,"led-0-0-0+15")
37
+ self.assertEqual(led4.hw_drivers["white"][0].number,"led-0-0-0+15")
@@ -100,17 +100,17 @@ class TestRandomEventPlayerBase():
100
100
  self.runner.advance_time_and_run()
101
101
  self.runner.assertNotEqual(self.prevEvent, self.lastEvent)
102
102
  self.runner.assertEqual(self.lastEvent, self.events[expectedIdx])
103
-
103
+
104
104
  def _test_conditional_random(self):
105
105
  self._reset()
106
-
106
+
107
107
  # Only one event is true
108
108
  results = list()
109
109
  for x in range(RANDOM_RUNS):
110
110
  self.runner.post_event("test_{}_conditional_random".format(self.scope))
111
111
  self.runner.advance_time_and_run()
112
112
  self.assertEqual(RANDOM_RUNS, results.count('event1'))
113
-
113
+
114
114
  # Conditional event kwarg is true
115
115
  results = list()
116
116
  for x in range(RANDOM_RUNS):
@@ -126,4 +126,4 @@ class TestRandomEventPlayerBase():
126
126
  self.runner.post_event("test_{}_conditional_random".format(self.scope))
127
127
  self.runner.advance_time_and_run()
128
128
  self.assertAlmostEqual(RANDOM_RUNS / 2, results.count('event1'), delta=3)
129
- self.assertAlmostEqual(RANDOM_RUNS / 2, results.count('event4'), delta=3)
129
+ self.assertAlmostEqual(RANDOM_RUNS / 2, results.count('event4'), delta=3)
@@ -24,3 +24,19 @@ class TestSettings(MpfTestCase):
24
24
  self.assertEqual("one", self.machine.settings.custom_setting_str)
25
25
  self.machine.settings.set_setting_value("custom_setting_str", "zero")
26
26
  self.assertEqual("zero", self.machine.settings.custom_setting_str)
27
+
28
+ self.assertEqual(0.1, self.machine.settings.custom_setting_float)
29
+ self.machine.settings.set_setting_value("custom_setting_float", 2.5)
30
+ self.assertEqual(2.5, self.machine.settings.custom_setting_float)
31
+ self.machine.settings.set_setting_value("custom_setting_float", 0)
32
+ self.assertEqual(0, self.machine.settings.custom_setting_float)
33
+
34
+ self.assertEqual(True, self.machine.settings.custom_setting_bool_t)
35
+ self.machine.settings.set_setting_value("custom_setting_bool_t", False)
36
+ self.assertEqual(False, self.machine.settings.custom_setting_bool_t)
37
+ self.machine.settings.set_setting_value("custom_setting_bool_t", True)
38
+ self.assertEqual(True, self.machine.settings.custom_setting_bool_t)
39
+
40
+ self.assertEqual(False, self.machine.settings.custom_setting_bool_f)
41
+ self.machine.settings.set_setting_value("custom_setting_bool_f", True)
42
+ self.assertEqual(True, self.machine.settings.custom_setting_bool_f)
@@ -5,13 +5,60 @@ from mpf.core.utility_functions import Util
5
5
  class TestUtil(unittest.TestCase):
6
6
 
7
7
  def test_string_to_ms(self):
8
- self.assertEqual(86400000, Util.string_to_ms('1d'))
9
- self.assertEqual(3600000, Util.string_to_ms('1h'))
10
- self.assertEqual(60000, Util.string_to_ms('1m'))
11
- self.assertEqual(1000, Util.string_to_ms('1s'))
12
- self.assertEqual(1, Util.string_to_ms('1ms'))
13
- self.assertEqual(0, Util.string_to_ms(None))
14
- self.assertEqual(0, Util.string_to_ms(False))
8
+ # Base Units
9
+ self.assertEqual(Util.string_to_ms('1d'), 86400000)
10
+ self.assertEqual(Util.string_to_ms('1h'), 3600000)
11
+ self.assertEqual(Util.string_to_ms('1m'), 60000)
12
+ self.assertEqual(Util.string_to_ms('1s'), 1000)
13
+ self.assertEqual(Util.string_to_ms('1ms'), 1)
14
+
15
+ # Quirky Units
16
+ self.assertEqual(Util.string_to_ms('200msec'), 200)
17
+ self.assertEqual(Util.string_to_ms('200Msec'), 200)
18
+ self.assertEqual(Util.string_to_ms('2sec'), 2000)
19
+ self.assertEqual(Util.string_to_ms('2Sec'), 2000)
20
+ self.assertEqual(Util.string_to_ms('1.5S'), 1500)
21
+ self.assertEqual(Util.string_to_ms('100.0ms'), 100)
22
+
23
+ # Unitless int/floats as strings
24
+ self.assertEqual(Util.string_to_ms('200'), 200)
25
+ self.assertEqual(Util.string_to_ms('100.0'), 100)
26
+
27
+ # Actual int/float
28
+ self.assertEqual(Util.string_to_ms(500), 500)
29
+ self.assertEqual(Util.string_to_ms(1.5), 1)
30
+
31
+ # Strings implying zero
32
+ self.assertEqual(Util.string_to_ms('None'), 0)
33
+ self.assertEqual(Util.string_to_ms('NONE'), 0)
34
+
35
+ # T/F/N native behavior
36
+ self.assertEqual(Util.string_to_ms(True), 1)
37
+ self.assertEqual(Util.string_to_ms(False), 0)
38
+ self.assertEqual(Util.string_to_ms(None), 0)
39
+
40
+ # Whitespace handling
41
+ self.assertEqual(Util.string_to_ms(' 500ms '), 500)
42
+ self.assertEqual(Util.string_to_ms('2s '), 2000)
43
+
44
+ def test_string_to_ms_unhandled(self):
45
+ with self.assertRaises(ValueError):
46
+ Util.string_to_ms('')
47
+
48
+ with self.assertRaises(ValueError):
49
+ Util.string_to_ms('no')
50
+
51
+ with self.assertRaises(ValueError):
52
+ Util.string_to_ms('pinball')
53
+
54
+ with self.assertRaises(ValueError):
55
+ Util.string_to_ms('True')
56
+
57
+ with self.assertRaises(ValueError):
58
+ Util.string_to_ms('200mss')
59
+
60
+ with self.assertRaises(ValueError):
61
+ Util.string_to_ms('2s 1s')
15
62
 
16
63
  def test_keys_to_lower(self):
17
64
  inner_dict = dict(key1=1, Key2=2)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mpf
3
- Version: 0.81.0.dev2
3
+ Version: 0.81.0.dev3
4
4
  Summary: The Mission Pinball Framework (MPF)
5
5
  Author-email: The Mission Pinball Framework Team <brian@missionpinball.org>
6
6
  License: MIT
@@ -85,7 +85,7 @@ What is Mission Pinball Framework?
85
85
  Mission Pinball Framework (MPF) is open source, cross-platform software for powering real pinball
86
86
  machines. MPF is a community-developed project released under the MIT license. It's supported by volunteers in their spare time.
87
87
 
88
- [![Coverage Status](https://coveralls.io/repos/missionpinball/mpf/badge.svg?branch=dev&service=github)](https://coveralls.io/github/missionpinball/mpf?branch=dev)
88
+ [![Coverage Status](https://coveralls.io/repos/github/missionpinball/mpf/badge.svg?branch=dev)](https://coveralls.io/github/missionpinball/mpf?branch=dev)
89
89
  [![Test Status](https://github.com/missionpinball/mpf/actions/workflows/run_tests.yml/badge.svg)](https://github.com/missionpinball/mpf/actions/workflows/run_tests.yml)
90
90
  [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/1687/badge)](https://bestpractices.coreinfrastructure.org/projects/1687)
91
91
 
@@ -1,7 +1,7 @@
1
1
  mpf/__init__.py,sha256=ycmoyzDc1kSASShp-FwsBhnBrCuVw3uXr58b_MbffIE,73
2
2
  mpf/__main__.py,sha256=8eo0swsTb94xUqTnjo7uE0oyz9ita3Pqpk85t2BYs1w,294
3
- mpf/_version.py,sha256=CAKFLv09Zhe0fQyDEtdpGrU_YowPM5AZeELmCD1TKWg,1789
4
- mpf/config_spec.yaml,sha256=GP85RWa4CPLr0AjOmMWvcUQ_Yy5t5pyxnjffy9hFMoE,71037
3
+ mpf/_version.py,sha256=YJAG6WLo_IBHGOBCSPa4MjktqgHH2JlWbH9I6E165JU,1789
4
+ mpf/config_spec.yaml,sha256=j2nIb18HQAzE2ePjjKvNx7wwfAkJ1vh3IGi7l74xqUU,71074
5
5
  mpf/mpfconfig.yaml,sha256=JZ_X-Uvm1JaXOy6_fxLauzBuCu99aClAQTs4J9I20XA,14757
6
6
  mpf/assets/__init__.py,sha256=AhIAlZFZRmGQzlTDD7nfOfrjF1pE82FNhWVnuTb8MFA,58
7
7
  mpf/assets/show.py,sha256=lHKhCGmsF1PN52lQ_XnMMCPYNq7On5J9YXm_05P4fQY,31350
@@ -72,10 +72,10 @@ mpf/core/custom_code.py,sha256=Zoxo8u5l0vwLjbe8D5SoHzcDIPi5mdx4mXbSTK_EojE,891
72
72
  mpf/core/data_manager.py,sha256=GeeuRpDRL-pEkCBz_2YZOihNZCE0Kv1-2I4ymYnz1kc,4775
73
73
  mpf/core/delays.py,sha256=iv9Hyj28897mdGVLv0c6oTRDpupI4PGkuWhC1JDncfg,6709
74
74
  mpf/core/device.py,sha256=jnUiuTmTdKryS_rwptipWvEVJ7_OSOPL0Jau1bMMthg,4924
75
- mpf/core/device_manager.py,sha256=UQ4IaQnNN6YwIa_vb83p1qyAxrfp9jzonNoDJBUW3fU,13191
75
+ mpf/core/device_manager.py,sha256=Nt0DnRO3qmYmxVCs-NlpyIXQ5-fnGxqnCL60-pYtuE4,13668
76
76
  mpf/core/device_monitor.py,sha256=EbscdXm0DK7PPwWkJFdCkIWHoIEHOrM3c5aBMIPIojU,4399
77
77
  mpf/core/enable_disable_mixin.py,sha256=twqqbA0U1W2ipNmMdDPu4vSeEU47Op3m5Z2HL-xqP-0,5621
78
- mpf/core/events.py,sha256=tUNXwSZXvBsczm19u_z9aQen1OpAP_-VNK85HoVuglU,42036
78
+ mpf/core/events.py,sha256=jIpaiDKe2imqiPYC9qLnZdxd3kQk3kVG4ThGZPMwFXg,42260
79
79
  mpf/core/file_interface.py,sha256=vGon_Mrj5z6rFohXJJT-smRE9X1iE21nxIfwTeZgJAU,1644
80
80
  mpf/core/file_manager.py,sha256=0wP2bE_zI9w1Z9o2tklyjmPt5mDGrPu7sN4Sv_gQon8,4087
81
81
  mpf/core/light_controller.py,sha256=vFvKSY3fq6VbgRHpFP1eeCsU0zVvUQ5kf1kNtj7jc_k,4752
@@ -86,7 +86,7 @@ mpf/core/mode.py,sha256=YlGj7VczEUwD1ESDStkZhKaqxNYctqR5MmFICdSAYDM,23375
86
86
  mpf/core/mode_controller.py,sha256=7eaK2-LxcVlOX0QK2oefDMNQbNLNSunCwOtPwMAbeGU,14053
87
87
  mpf/core/mode_device.py,sha256=C9E9eiKPX9R9RgBDpuwUwuObeYvkkgM7Qs62gONlS2o,2589
88
88
  mpf/core/mpf_controller.py,sha256=R_kfMHKD6xYzGEpnIJgxY0GCtqBlxK7dE7ysyE5BxDg,1170
89
- mpf/core/placeholder_manager.py,sha256=CGoM-suQytxvUJsANdI64A6BAK4YneAve83N5RiOCrY,31679
89
+ mpf/core/placeholder_manager.py,sha256=YmHiZeOp7baPrRhmc8r69SUMO1KKOLbhiCkIJKTThTs,32447
90
90
  mpf/core/platform.py,sha256=_9eh9ybl2ep6JNgOaOqpsXjslCorf18NQH1_hwFCEk4,27086
91
91
  mpf/core/platform_batch_light_system.py,sha256=YQXNmXJkdP41Gw7PgVk92gnu1eaMn3YdHjDSUAqLoew,7892
92
92
  mpf/core/platform_controller.py,sha256=oniy9CgK3ki9ENsMJJqvTF6NA5IxaJWPca2Fj5OLJoc,23725
@@ -102,7 +102,7 @@ mpf/core/show_controller.py,sha256=C9wtABHGZZ78vyHuFtS0gj6qFaXl_YTiLaGMf2iMMZg,6
102
102
  mpf/core/switch_controller.py,sha256=xwY7uc0UuixfeYtraUbh-XfpFitNzU3W2E6w-9bsyGc,30662
103
103
  mpf/core/system_wide_device.py,sha256=5e_nB3zYZSTjq_wTcbckSw7vxLtW7idIGk44Yby-cXE,431
104
104
  mpf/core/text_ui.py,sha256=sQNIqzTpkBS1gtjsQ3qDO_YcyzCuz0X6ViHgLhaH2jY,19088
105
- mpf/core/utility_functions.py,sha256=lY_EeksSDiooVgwfMATlFhOCYn3Z5IELaDOT3KqWoCU,30245
105
+ mpf/core/utility_functions.py,sha256=XILkH65fVA4g0ikCEHoYeTyIT2pNw6WN89HCaE17_G0,30414
106
106
  mpf/core/bcp/__init__.py,sha256=MdDZJevywlYvFsmDNEOveGV-IiYMlR11c8bcIflo3zw,47
107
107
  mpf/core/bcp/bcp.py,sha256=CVJSVqDhDghJAQcL_hG6eiG9ukzEhGqF7OIq-nvYx0Y,4750
108
108
  mpf/core/bcp/bcp_client.py,sha256=o9sCF34Er-_c8aS5llok1bUoN9zk94lEZn8yTvGUpAI,1085
@@ -135,7 +135,7 @@ mpf/devices/flipper.py,sha256=QZIy0Zvkxhs3wlmp-uTd59awGF46_XuLYhyyTEOGMbg,12209
135
135
  mpf/devices/hardware_sound_system.py,sha256=XFq8LtyxwlDyCtZYvk-JSOms-CTAnTK4KXGOjeV60MQ,2406
136
136
  mpf/devices/kickback.py,sha256=ZkXvGDojfCF0w3Y4-QE6qX963tDDAN6zQ-XZ1bckG3g,748
137
137
  mpf/devices/light.py,sha256=pPBMlx0GM9OYiJfXxMmUV0IDUscPYidqEv7BGAnVE-g,36608
138
- mpf/devices/light_group.py,sha256=h9e5tBYI_kCva491P84LX71AVcweCaXFh72vZ2jwgG8,6768
138
+ mpf/devices/light_group.py,sha256=PcujFBLSL1fCSOI7kQSQ-bu87n8ordvGBLbctGwpe4Y,7912
139
139
  mpf/devices/logic_blocks.py,sha256=paZV6FOuqSOEq2o4-bKNxKNJCUzbO0SNLDIbp9mnKPQ,24340
140
140
  mpf/devices/magnet.py,sha256=GsTTg4Te4OHRz-ZQJxIr8vTj86_VVg2xBXdx6iK88o0,5420
141
141
  mpf/devices/motor.py,sha256=014xbRpO6Je-glD1jz9hz7MuafBw_q2aKm2yrPHxZ5E,9728
@@ -266,7 +266,7 @@ mpf/platforms/virtual.py,sha256=NfyI80ZEnnAspDmy92K7AMjjisQ4KaCmzD7SuplzoWo,2608
266
266
  mpf/platforms/zedmd.py,sha256=hx0PXXbxJa35vufUgHByDLdpwMbkQaOmrcCz76YvFk4,4139
267
267
  mpf/platforms/fast/TODO.md,sha256=WzddBMV7jfNwzkzkYovpmYGX_NMGMQhTL0R7fkKLmIY,3087
268
268
  mpf/platforms/fast/__init__.py,sha256=79CsU79usuvjebPggPmvlZg5DrzdQ_nCTHGow-2T_V4,30
269
- mpf/platforms/fast/fast.py,sha256=HlQimgIlu9FoIpk-gKch25Sxhmj9aLtAwCErSFl-3_0,43380
269
+ mpf/platforms/fast/fast.py,sha256=9fD00hzeRpd274TYDoUVIYKpZWA9NCdOM41pIeaIJe0,43553
270
270
  mpf/platforms/fast/fast_audio.py,sha256=OpSJZ1bU3qyLP_xWDPOfsGZYkmQHYhOOvcs4V5Ee17c,10281
271
271
  mpf/platforms/fast/fast_defines.py,sha256=tDHaJzF4i3PmQDf3Ymn06xC17irDkFsVcmXrrPaADTA,7300
272
272
  mpf/platforms/fast/fast_dmd.py,sha256=2wmbj95htWMRQWK2T83E9KCcRT2Q4sfJ5BkKOZQHy2M,662
@@ -274,7 +274,7 @@ mpf/platforms/fast/fast_driver.py,sha256=7mnn4_6WNjI8auWgIQXCfu7nUV3cA_umfQtH-n3
274
274
  mpf/platforms/fast/fast_exp_board.py,sha256=SC0h_SYkOsuwYLhOwIlt9U3P1Evy_6kKFr33-RjxTiQ,14904
275
275
  mpf/platforms/fast/fast_gi.py,sha256=daV7B7X1ApGkjzbd4YGxJ1AoRgoIC1Fx0UcBRquTzqU,1630
276
276
  mpf/platforms/fast/fast_io_board.py,sha256=psNIrZ3GooNejhfXy1Z7_KXtv6jdw6j4vzsj81CjSx0,1275
277
- mpf/platforms/fast/fast_led.py,sha256=gZ334LnVpaI0CSxs1JBfjvUrTXJcZ4r6XUTDrdVUhsI,6911
277
+ mpf/platforms/fast/fast_led.py,sha256=HdgmqOAkGRR4v1bN2WFR8UO608kMDPZ2m5u7EiRmFcc,7605
278
278
  mpf/platforms/fast/fast_light.py,sha256=UeUdeRHgfrRpdZDd1KtNlmClkk3C0oRBQJahQHoMZSE,1905
279
279
  mpf/platforms/fast/fast_port_detector.py,sha256=jt8L3aIey1HZXiLUnnSSlg3aze2mzp8Cjb7ci_a-QQw,5022
280
280
  mpf/platforms/fast/fast_segment_display.py,sha256=DOrzzAWF0ztJHYK2yl2kLx_MUghBEbMVzungeOxSoyo,1739
@@ -350,7 +350,7 @@ mpf/platforms/trinamics/README,sha256=en6fywA6uVtkVWtnSh99qA7lIGBqtg-57xudNqRG1O
350
350
  mpf/platforms/trinamics/TMCL.py,sha256=yKzBBHqbFFw8_quIldt2oh1iVtSkX-xChLLlgRa3f2A,30543
351
351
  mpf/platforms/trinamics/__init__.py,sha256=w8UfkzSsOjidkOajJVAkRw1AXeFRN77jHPCyVh7YK18,30
352
352
  mpf/platforms/virtual_pinball/__init__.py,sha256=YBWkKGkc5GItvkPUFb6LMn5VYDoKH6uAPaQ0CDmNHU4,27
353
- mpf/platforms/virtual_pinball/virtual_pinball.py,sha256=QaXkCidsdDJEQhasW7zf5D0zhPhZUY7GmRxPgRoUdHU,19164
353
+ mpf/platforms/virtual_pinball/virtual_pinball.py,sha256=TK5Zse36u98mGXBUebXZLouqZrJLFgg-oTBSPWyTgTI,19163
354
354
  mpf/platforms/visual_pinball_engine/__init__.py,sha256=8nICCe2NaXpfTUSTIA8VEVzuYxQaR9U232_9VoHTGNI,27
355
355
  mpf/platforms/visual_pinball_engine/generate.py,sha256=2xofapyggJEstxbOsCfdFvVsM7QZxeEgEuAeJAH0qwQ,588
356
356
  mpf/platforms/visual_pinball_engine/platform_pb2.py,sha256=5pPiv7pKibQ0GuPCv-CPZubwJUroKqcfXqCST1ofZvg,8781
@@ -450,7 +450,7 @@ mpf/tests/test_ConfigOldVersion.py,sha256=l1Uf-ZAxHGTbfIwJZRPggearqyD075MzKa7VTd
450
450
  mpf/tests/test_ConfigPlayers.py,sha256=hiNYK0puN0IE_hPfhVMrYQ5MSryUdI0xNY8ah86afSg,4919
451
451
  mpf/tests/test_ConfigProcessor.py,sha256=xzZ-kSooPX-AewZoDwU3sTXSFOPZ_QR5032igowoh3U,2497
452
452
  mpf/tests/test_CreditsMode.py,sha256=fR4kCC2HTTA9-bcZ2SNsM925mT9xAPJzA85NN8sHjHQ,17788
453
- mpf/tests/test_CustomCode.py,sha256=rMRgfM1AwYecEDnzVzNWlteGFP0xKo29vn10sCt8dz8,445
453
+ mpf/tests/test_CustomCode.py,sha256=xPsQ6Ijs-kKbu05kWBRYm20olNXF8EuLdI21tWeYFrY,648
454
454
  mpf/tests/test_DataManager.py,sha256=WOC_AwrtXt5mzBBCqZ4Z2UWyZiP6fjn6I-iURwfYRJA,5350
455
455
  mpf/tests/test_Delay.py,sha256=r-ijAjNm4QQ5YRj8iA5ACQ14M2drU-nqir6fqFrBTos,4931
456
456
  mpf/tests/test_DeviceCollection.py,sha256=twCg7qAY6CYMw9F3rwxI0NP64gMHe9WhOlhsAgckvXE,1770
@@ -458,7 +458,7 @@ mpf/tests/test_DeviceDriver.py,sha256=Un6fAW_-wjmjX-GiiwdhuDXz8kTgW5r6jjASh-gdYe
458
458
  mpf/tests/test_DeviceFlasher.py,sha256=DbBULJDCL2KBsAepLtKXEUwP38Zb0aJVA42ncgNfNog,2201
459
459
  mpf/tests/test_DeviceGI.py,sha256=8IlcdwJPf1rMaofHYPBX8m82fwUgystmPyDjBbGicqw,1358
460
460
  mpf/tests/test_DeviceLight.py,sha256=LW2XoIBCh8O2U3eSE1eRvYcQD7n2_Fqf7v7fApa1M5c,22534
461
- mpf/tests/test_DeviceManager.py,sha256=fvINpL51HaiQHdzyCSzNM7Ur-L8kaOX-WF1TPg_dPcM,1741
461
+ mpf/tests/test_DeviceManager.py,sha256=6BnNBgdNywipOwaRDzuqNwCbu_vPgjBGr9JSmTfA9pg,4153
462
462
  mpf/tests/test_DeviceMatrixLight.py,sha256=siJaDyaickqj9Vv1Qr5oD9Yf0YrZl-OIZPk1sOfdGiQ,2513
463
463
  mpf/tests/test_DigitalOutput.py,sha256=INYIhtJW0OVDsoUdq--4Fb-iHiqcKUfpbXbzV8cEzFQ,1256
464
464
  mpf/tests/test_DigitalScoreReels.py,sha256=BrCj_VovVvFi84ewPaFPKL6AO-p9Nnu6LBTkmCZJbLI,1905
@@ -466,7 +466,7 @@ mpf/tests/test_Diverter.py,sha256=12BHmcDmZz2yEuz4O6RvhHKdWO_SnZ0MdfJHZTuSKhE,26
466
466
  mpf/tests/test_Dmd.py,sha256=3caBAYOPNynnLzNZzjTvwxJApoiRRzdQlWoStIyEZRo,1332
467
467
  mpf/tests/test_DropTargets.py,sha256=BjowOQFA5fTkLR15StArXD_FBZxkrq86dtZ_GHsPNSc,18671
468
468
  mpf/tests/test_DualWoundCoil.py,sha256=rBfL56OOnY1ZzBxlZlPUeGsLtf9UlkyAdQRiGd2W28U,1883
469
- mpf/tests/test_EventManager.py,sha256=VEUHITtxApA3vHP0yN9CtaCUw7ybZJjAbbnNYY1JCK4,35867
469
+ mpf/tests/test_EventManager.py,sha256=9KLT-zC9MBzhLhCAdKBQfv834ZyPHpEd_utPiOlK8po,37799
470
470
  mpf/tests/test_EventPlayer.py,sha256=rV1HK2C6iPvace-aD1zlLkdsUfYCvbBh1eNeVnY0G9M,10354
471
471
  mpf/tests/test_ExtraBall.py,sha256=xAt2s8IP4Eh7fAi2kPbKrw7pchpXsfVrLKEaPbcZiCQ,16388
472
472
  mpf/tests/test_Fadecandy.py,sha256=y7ZQkdGYmG6WXoi6kKMTxdRYymPhOc9SqHzbrkK9V60,8133
@@ -474,6 +474,7 @@ mpf/tests/test_Fast.py,sha256=6bbS_z3Dv6KP9GDirL7IhEnzCZ_2JfEkQNGPWDTCUGc,10711
474
474
  mpf/tests/test_Fast_Audio.py,sha256=50O_tWU350s1WXrre5X0TY9wmwgKRsyKph1Wvikr8Fg,6008
475
475
  mpf/tests/test_Fast_Dmd.py,sha256=0htPLOemtTi1EZa7LKVkANaP5RH_HpmknFKBKvePhFI,800
476
476
  mpf/tests/test_Fast_Exp.py,sha256=sxUe48fvFdzwqE_C4L7A9G1J5T96HuMWFkA4tzYdd7U,11324
477
+ mpf/tests/test_Fast_Led.py,sha256=c26fRdwODDlsC1M6sue9CHCDyq0pdhr9d4wrUc7QTbs,2240
477
478
  mpf/tests/test_Fast_Nano.py,sha256=OcxiGiche1BfTulxchyePdbmJat6tTxVWyugNnAnBzw,33999
478
479
  mpf/tests/test_Fast_Neuron.py,sha256=c038-jFbv3f3TF_fPnNhRm-PY55jF_5uXOWwNz2QViA,40225
479
480
  mpf/tests/test_Fast_Retro.py,sha256=N-xiiNnMdY-KwB2zFyXhd1u9L0yMAGoPqS-i76bNAeA,19814
@@ -489,8 +490,8 @@ mpf/tests/test_HighScoreModeWithVars.py,sha256=zGrpvxJbxUteanOttPwmCLqUhPBPmQ-Xc
489
490
  mpf/tests/test_I2cServoController.py,sha256=osRF2mqK5BcYikb5iHTQVfkFCxN5T5vPlukSOR93lJ8,2493
490
491
  mpf/tests/test_InfoLights.py,sha256=ahslOuYGEDKwIyZ4lWU-4iwInHimfN0TnYwTxjqerpA,3370
491
492
  mpf/tests/test_Kickback.py,sha256=VQXMmVnfVNgfuKf9UnSjfJKOxcQfzznDqKBDykZIuY0,2119
492
- mpf/tests/test_LightGroups.py,sha256=bEN-7cPFwsR59JO-qOj5c0HrT6PRTw-GJT2fus3hyHg,6741
493
- mpf/tests/test_LightNumbering.py,sha256=mdJGj-HHsCGroNRp6_JPKExYPFzumVnanMN-qYG3IXA,1708
493
+ mpf/tests/test_LightGroups.py,sha256=jIGBCrL63Mi6Oplq8P6pS3qw_WvXWARJ_2VnaXA5LU0,10570
494
+ mpf/tests/test_LightNumbering.py,sha256=Hpgv0e5YbszlZJF_ZNEtcI3_FxssJJTsXdRqPaHZyC8,1701
494
495
  mpf/tests/test_LightPlayer.py,sha256=KieLKDzYtNF6XMHnHu1ez3f7AtjnuSh3EeB40buyRNo,15265
495
496
  mpf/tests/test_LightPositions.py,sha256=zAbifSeIqEQmNU5gZS3ImXuJY5PtrVW_qhrKjxhmQE0,834
496
497
  mpf/tests/test_LightSegmentDisplays.py,sha256=vvTIZQGQhzFOilWcKa_GwIulYyJS1pOHHdVV7dgRWKI,8866
@@ -523,7 +524,7 @@ mpf/tests/test_PololuMaestro.py,sha256=guKXqbW92Yu-FodrjRyr8Y1dBw6Y8lJvE48LMxpy5
523
524
  mpf/tests/test_PololuTic.py,sha256=WaKHMazooOok5PcQjaSurScO9FgUjc7aALd8DebQmi4,5033
524
525
  mpf/tests/test_QueueEventPlayer.py,sha256=hWX0sScrDgTVPr5q9vwH6lpe5lif6rhjTlc_kv9qGqM,4786
525
526
  mpf/tests/test_RGBColor.py,sha256=upiXfSN0rtDpgwFjPCC1jutuRwVcPfgWjYBgT8X7IKM,6517
526
- mpf/tests/test_RandomEventPlayer.py,sha256=iFi2CYKgFJSiA19--CO3yFH1KscmcKhpdoBzqXt0vZ0,4972
527
+ mpf/tests/test_RandomEventPlayer.py,sha256=7L46cleZtx-Ka2mu0DaYOKweOl3iv267jmymfUI0mO0,4953
527
528
  mpf/tests/test_Randomizer.py,sha256=-LuhPfvDHyX0jkC3LZdZBUO7Dg1iKYSlpfsJwAzsj9o,11692
528
529
  mpf/tests/test_Rpi.py,sha256=QhHuLo7SsUtLfU8mNl_3HktGHn0pKLNG5VoT2-jWN0E,5703
529
530
  mpf/tests/test_RpiDmd.py,sha256=khG8rV34wZJ0mNSW0CCqNolXonGaBFojk5AUKLDeaT0,1584
@@ -535,7 +536,7 @@ mpf/tests/test_SequenceShot.py,sha256=cjgz717-fpfzyjr1nKueJncRqgfqXV9KIu5pp0UqbM
535
536
  mpf/tests/test_ServiceCli.py,sha256=vC5ph_zFMxRx5Jnet7ZY4LRSwACusbYUpie1S4AyxzM,5184
536
537
  mpf/tests/test_ServiceMode.py,sha256=Ohh6kcPBaha5xUL3ilay2YO-ZHJQOLdU2HDlJcMimL0,17058
537
538
  mpf/tests/test_Servo.py,sha256=Ge5qjUCFUkUgz2LeeN2XiHiD2A4jZnCB-UmlPWuYKCE,2927
538
- mpf/tests/test_Settings.py,sha256=ZuDgdb1h5Ui0atT2Q3Quosgr1xINfk9W-8Apwhd3yag,844
539
+ mpf/tests/test_Settings.py,sha256=3MbSiIZnXelcaxqbHRXCP0_8worAWL-BFdKLjNQmkYY,1839
539
540
  mpf/tests/test_ShotGroups.py,sha256=mwSa8z2wcg02IeecQCrROZRutsGyb9Y1bjuoP0dzESE,18353
540
541
  mpf/tests/test_Shots.py,sha256=tftWsVXkiCXhNJ0EcUd92pDd5f8KHvBxYRnuor7xyRs,37943
541
542
  mpf/tests/test_ShowPools.py,sha256=kPdZLW0bqLeji60_kInCFA4ZWY8YyhewH38tZDoP5Mg,2485
@@ -561,7 +562,7 @@ mpf/tests/test_TooLongExitCountDelay.py,sha256=2qpMlq1lif4xmPn-VXj3vQG5QDufhJz2G
561
562
  mpf/tests/test_TrinamicsStepRocker.py,sha256=KWUD1JVf4R81f5Du1xyX2oQYOonxyInoItO5QzafscM,3289
562
563
  mpf/tests/test_TroughEntranceSwitch.py,sha256=PRCTPxtJ2D4j0zZhY9SXO7SFUkJcUF1zHgE_uRKPsgc,1654
563
564
  mpf/tests/test_TwitchClient.py,sha256=2XJNcaNkgsfyZCk0BaVFsJCRus5nSg4hHE6-uHaGil0,6993
564
- mpf/tests/test_Utility_Functions.py,sha256=s3SAf5nQF0qCS24zGdCTO6H2LE_F-tooscFIvvuVhqY,8621
565
+ mpf/tests/test_Utility_Functions.py,sha256=_JTtApKS6_hvmEEKm9vpuzMeTORQCDdfZkZ2PzPvsJo,10230
565
566
  mpf/tests/test_VPX.py,sha256=-RhGgzWHCFfv0meRY4jMXDaltmSs5J4b5ba8qkzrtU0,7802
566
567
  mpf/tests/test_VariablePlayer.py,sha256=Pe2TihiarrjsL1knzViv52gu5jo5BlurNH8GCczPXvc,10282
567
568
  mpf/tests/test_Virtual.py,sha256=hiiUjIVY3bmFZvRKgBTOhVmlV2ASzfML4o8pRhsz3CI,612
@@ -592,7 +593,7 @@ mpf/tests/machine_files/asset_manager/shows/custom1/show13.yaml,sha256=_P-fejT-l
592
593
  mpf/tests/machine_files/asset_manager/shows/on_demand/show5.yaml,sha256=_P-fejT-lM_Ri5FA0GP9tAY7ILw9h6oaW4nbt-kbIjw,527
593
594
  mpf/tests/machine_files/asset_manager/shows/preload/show4.yaml,sha256=_P-fejT-lM_Ri5FA0GP9tAY7ILw9h6oaW4nbt-kbIjw,527
594
595
  mpf/tests/machine_files/asset_manager/shows/preload/subfolder/show4b.yaml,sha256=_P-fejT-lM_Ri5FA0GP9tAY7ILw9h6oaW4nbt-kbIjw,527
595
- mpf/tests/machine_files/auditor/config/config.yaml,sha256=57a420Y5BCJcirw57pxx6SovwyXdEv7gxjYo9WW8L54,388
596
+ mpf/tests/machine_files/auditor/config/config.yaml,sha256=Ao7PrPMiBrRXk0WEdp3-0TsSKZ4_PtVnsE_4vsqtD0s,387
596
597
  mpf/tests/machine_files/auditor/modes/base/config/base.yaml,sha256=HcnCQGbaVT6i9NJwUNL5fWj36Jhcnl7zykghnPGrY6A,261
597
598
  mpf/tests/machine_files/autofire/config/config.yaml,sha256=7LL4RB24kSi7eQICGRdWbRxFmLIWKJZ7DSEHuZYyAFs,1487
598
599
  mpf/tests/machine_files/ball_controller/config/config.yaml,sha256=a6DMjD4_Xts1KJ3UBDDtDgbybYKdDaEHjOHN6feYdyE,1068
@@ -697,7 +698,7 @@ mpf/tests/machine_files/credits/config/config_freeplay.yaml,sha256=6JMbjmjsS8yK8
697
698
  mpf/tests/machine_files/credits/config/config_inhibit.yaml,sha256=MYAjgeSHE6iV6rh1YV4a3-WEI_5qv-3QrYYn_35YOkY,367
698
699
  mpf/tests/machine_files/credits/modes/credits/config/config.yaml,sha256=N9dGGiwQH67CsEhAtWE31OwumVGyoztTVzaGkRXs9zY,107
699
700
  mpf/tests/machine_files/custom_code/code/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
700
- mpf/tests/machine_files/custom_code/code/test_code.py,sha256=mzIpK_mAg0c_DsJ43zAb_8ekRoLr89cwc34F3AfpvCI,309
701
+ mpf/tests/machine_files/custom_code/code/test_code.py,sha256=KHrkCVTVhvoW_uCmhA2JoI6XcTXneLCKLSVxg90Dk_E,490
701
702
  mpf/tests/machine_files/custom_code/config/config.yaml,sha256=roJzS_8FhnvK1UzWG9DyqPpG0N1NMmZm7PtbQSR4az8,380
702
703
  mpf/tests/machine_files/data_manager/config/config.yaml,sha256=I0aiEhNSdlLMRlJKtcbAgPV_48725L7lDWr3wbqjx70,164
703
704
  mpf/tests/machine_files/device/config/coils.yaml,sha256=HlzikHgj_AGzGp0oll3IITEkRONMEkKN8gZP8cq67zQ,533
@@ -726,8 +727,8 @@ mpf/tests/machine_files/dmd/config/testRgbDmd.yaml,sha256=3bgVH7UjPRHLZbYw1q_n43
726
727
  mpf/tests/machine_files/drop_targets/config/test_drop_targets.yaml,sha256=58vBTtYMhdwkjW9TpJDWQQWFdjkRcYUyKlK3BXQeXFE,1710
727
728
  mpf/tests/machine_files/drop_targets/config/test_multiple_drop_resets_on_startup.yaml,sha256=ht-FfB-8ZjujnGSWcW9DN4F9zKIcp82AQaj90ElYNSU,337
728
729
  mpf/tests/machine_files/drop_targets/modes/mode1/config/mode1.yaml,sha256=znSa7PmrvBTspwQ34o7fSlCl3n5NtvLWizI-3_0Su4Q,186
729
- mpf/tests/machine_files/event_manager/config/test_event_manager.yaml,sha256=F6ZBH8l6teXjiFJnB8h_dDbx4X6dJNX_1uPyEwNJqeY,406
730
- mpf/tests/machine_files/event_manager/modes/game_mode/config/game_mode.yaml,sha256=TI77DsAmpqUBxmpchBEqdRO7t6m7XJ3Rn6RMi5HBnOE,172
730
+ mpf/tests/machine_files/event_manager/config/test_event_manager.yaml,sha256=r0bFMKucXaxG68fqCThIGBCPXSUWHRNaktlnx0RpX88,582
731
+ mpf/tests/machine_files/event_manager/modes/game_mode/config/game_mode.yaml,sha256=Wcvh7mSDILzKTrv7Lh3IPjtlCAnFgs7AsC2TGu9lBr4,421
731
732
  mpf/tests/machine_files/event_manager/modes/test_mode/config/test_mode.yaml,sha256=8_LOBAMFabPcT1u-fncfQ0NsPW0UatSsUxquU4FOgVA,552
732
733
  mpf/tests/machine_files/event_players/config/test_event_player.yaml,sha256=oxWJElrLOfctXEXz_cDyabFYpJgfyh9qCNM3lYHGCYY,2092
733
734
  mpf/tests/machine_files/event_players/config/test_queue_event_player.yaml,sha256=-guARDhqONBsw__GRqBKwxwAufnehQrBz7YK0sQYWwg,414
@@ -763,7 +764,7 @@ mpf/tests/machine_files/kickback/config/config.yaml,sha256=VTfx491DKfYxcDdCnB2th
763
764
  mpf/tests/machine_files/light/config/light.yaml,sha256=4NjJ-yL1O9o_jNYO6NGy6DTt2BGzDMs86usnrBDVJ0o,964
764
765
  mpf/tests/machine_files/light/config/light_autonumber.yaml,sha256=ZAwGXze2wlk0FFBKgTAxz_beRFpItKSNRrwabX5dr4I,213
765
766
  mpf/tests/machine_files/light/config/light_default_color_correction.yaml,sha256=h7ZSv7uSJRG57hpxUqy7SMX4VqWftBk0b12YjuZ4fu0,309
766
- mpf/tests/machine_files/light/config/light_groups.yaml,sha256=TVZZrh58gCUVl3PCK6LOpwCJpMreMNGqZnM2h9RPwHw,833
767
+ mpf/tests/machine_files/light/config/light_groups.yaml,sha256=GEwMtLaa3OXUlkOMh4oCssYnBXz2q9-CZgahZJE6rvY,1071
767
768
  mpf/tests/machine_files/light/config/lights_on_drivers.yaml,sha256=Wc6GPnlAdL8D9H58SZQtjDY9Pc4c0Bz7ASMvafAwrPo,215
768
769
  mpf/tests/machine_files/light/config/matrix_lights.yaml,sha256=ntmFDNeNg8XLopCeyWwD3quh1qm7cxyylrK8zrFDBgs,178
769
770
  mpf/tests/machine_files/light_player/config/light_player.yaml,sha256=HhgenUx6hk65xibQwjXb08LGulu5fi_b25otcYU50DY,1177
@@ -881,9 +882,9 @@ mpf/tests/machine_files/segment_display/config/game.yaml,sha256=xCGQxOFaqm1mSl-K
881
882
  mpf/tests/machine_files/segment_display/modes/mode1/config/mode1.yaml,sha256=30Xo3grYQG3zypICRnb1mbz8mY78BDweMcajpINhkC0,173
882
883
  mpf/tests/machine_files/sequence_shot/config/config.yaml,sha256=ate99LsEy9czRLDJSyC1PFxxvxC15TsdlQ1xr-ZIiNA,2028
883
884
  mpf/tests/machine_files/sequence_shot/modes/mode1/config/mode1.yaml,sha256=jufw8bwnOoPtAwyGpouWNnmcpqvWxflb9zWYGWZcKxg,489
884
- mpf/tests/machine_files/service_mode/config/config.yaml,sha256=qvqf77vl8Sbqg5hm8b4TDjKD4seCu1MvGd8-TM_-Ss0,1417
885
+ mpf/tests/machine_files/service_mode/config/config.yaml,sha256=3tLwdjr6YR_vezJywbEAZvRj9hWF4F3glzl-vQlSYCw,1416
885
886
  mpf/tests/machine_files/servo/config/config.yaml,sha256=iJXZsC5xVrQnRkwF9adAxsuiBGiVBqGA4gSpFlKAFIQ,481
886
- mpf/tests/machine_files/settings/config/config.yaml,sha256=CERk0G2viSxZoIgdV79DPKg_jWFqxY6haFDbRfOh8Ec,423
887
+ mpf/tests/machine_files/settings/config/config.yaml,sha256=wqNkyp6lIGb5ds9OFKbTOl2yLJmnxIEo9J4Cm5Pbq-w,1064
887
888
  mpf/tests/machine_files/shots/config/test_shot_group_rotate_with_exclude.yaml,sha256=rDXihcEySJErepBjUEewpaPDm4xoVGPZ1pcZv3SYin8,209
888
889
  mpf/tests/machine_files/shots/config/test_shot_groups.yaml,sha256=pT808F42hPuEquQxRfpiClVZU336ZKYIU9GKCoP1wjQ,1678
889
890
  mpf/tests/machine_files/shots/config/test_shots.yaml,sha256=7nXSIgBmq75TumSfXAWlul5EzIxwCg_d-oYtA8Zbkjs,2461
@@ -960,10 +961,10 @@ mpf/tests/regression_tests/shots_with_token_in_profile_and_shot.yaml,sha256=uyRp
960
961
  mpf/tests/regression_tests/show_player_subscriptions.yaml,sha256=JJJ72Kmc6p4dQB9NA78DcR8JEbHTpurclYUCpTX8Cug,2206
961
962
  mpf/wire/base.py,sha256=6AsVN7gxAdSP40T1SIABplUbiYRqOp37x-2w_CE8fcQ,11581
962
963
  mpf/wire/fast.py,sha256=XmqBuwM1CZ102LQXNy26SqTy75HuOuCmfkQA9Xc2MiM,20521
963
- mpf-0.81.0.dev2.dist-info/licenses/AUTHORS.md,sha256=izUqN5WUC6FV2pUURPUiD308hYo_JREH_ENeTsEstLg,1445
964
- mpf-0.81.0.dev2.dist-info/licenses/LICENSE.md,sha256=dMfwSWLbBdRZPIahHwQoMq_dxiZxOxVRLiTZ6GE_5wo,1119
965
- mpf-0.81.0.dev2.dist-info/METADATA,sha256=X45Xqy8f28EQN3Geo9B25Dpu8qZTt70JsdIK6jG8TVY,6021
966
- mpf-0.81.0.dev2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
967
- mpf-0.81.0.dev2.dist-info/entry_points.txt,sha256=FMGV1EnYmb_MOBOb2nMg-kpgMbqk5HItDSA1rkkjnAM,59
968
- mpf-0.81.0.dev2.dist-info/top_level.txt,sha256=K6To2MlFIMFjYIQIVaql2hOPCpu_yzao6_OotT3n5pw,4
969
- mpf-0.81.0.dev2.dist-info/RECORD,,
964
+ mpf-0.81.0.dev3.dist-info/licenses/AUTHORS.md,sha256=izUqN5WUC6FV2pUURPUiD308hYo_JREH_ENeTsEstLg,1445
965
+ mpf-0.81.0.dev3.dist-info/licenses/LICENSE.md,sha256=dMfwSWLbBdRZPIahHwQoMq_dxiZxOxVRLiTZ6GE_5wo,1119
966
+ mpf-0.81.0.dev3.dist-info/METADATA,sha256=haS2MRSNrSOH-fSfOUwTQQHGzpnACGP6VeUiWETzw_0,6013
967
+ mpf-0.81.0.dev3.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
968
+ mpf-0.81.0.dev3.dist-info/entry_points.txt,sha256=FMGV1EnYmb_MOBOb2nMg-kpgMbqk5HItDSA1rkkjnAM,59
969
+ mpf-0.81.0.dev3.dist-info/top_level.txt,sha256=K6To2MlFIMFjYIQIVaql2hOPCpu_yzao6_OotT3n5pw,4
970
+ mpf-0.81.0.dev3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (82.0.1)
2
+ Generator: setuptools (83.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5