Skip to content

Commit

Permalink
refactor ejectors out of ball_device
Browse files Browse the repository at this point in the history
  • Loading branch information
jabdoa2 committed Sep 30, 2016
1 parent 8b3fa8f commit 9b0dcfc
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 105 deletions.
1 change: 1 addition & 0 deletions mpf/devices/ball_device/__init__.py
@@ -0,0 +1 @@
"""Ball device and ejectors."""
130 changes: 26 additions & 104 deletions mpf/devices/ball_device.py → mpf/devices/ball_device/ball_device.py
Expand Up @@ -2,13 +2,19 @@
"""Contains the base class for ball devices."""

from collections import deque

from mpf.devices.ball_device.hold_coil_ejector import HoldCoilEjector

from mpf.core.delays import DelayManager
from mpf.core.device_monitor import DeviceMonitor
from mpf.core.system_wide_device import SystemWideDevice
from mpf.core.utility_functions import Util


# pylint: disable-msg=too-many-instance-attributes
from mpf.devices.ball_device.pulse_coil_ejector import PulseCoilEjector


@DeviceMonitor("_state", "balls", "available_balls", "num_eject_attempts", "eject_queue", "eject_in_progress_target",
"mechanical_eject_in_progress", "_incoming_balls", "ball_requests", "trigger_event")
class BallDevice(SystemWideDevice):
Expand Down Expand Up @@ -94,9 +100,6 @@ def __init__(self, machine, name):
# when this device wasn't ready for them
# each tuple is (event wait queue from eject attempt event, source)

self.hold_release_in_progress = False
# flag for Whether there is a timed "hold" release in progress now

self._state = "invalid"
# Name of the state of this device

Expand All @@ -118,6 +121,7 @@ def __init__(self, machine, name):
self._idle_counted = None

self.eject_start_time = None
self.ejector = None

def _initialize(self):
"""Initialize right away."""
Expand Down Expand Up @@ -501,7 +505,7 @@ def _trigger_eject_by_event(self, **kwargs):

if self.mechanical_eject_in_progress:
self.mechanical_eject_in_progress = False
self._fire_eject_coil()
self.ejector.eject_one_ball()
else:
self._do_eject_attempt()

Expand Down Expand Up @@ -823,13 +827,6 @@ def _register_handlers(self):
ms=self.config['entrance_switch_full_timeout'],
callback=self._entrance_switch_full_handler)

# handle hold_coil activation when a ball hits a switch
for switch in self.config['hold_switches']:
self.machine.switch_controller.add_switch_handler(
switch_name=switch.name, state=1,
ms=0,
callback=self.hold)

# Configure event handlers to watch for target device status changes
for target in self.config['eject_targets']:
# Target device is now able to receive a ball
Expand All @@ -853,12 +850,18 @@ def _initialize_phase_2(self):
# validate that configuration is valid
self._validate_config()

if self.config['eject_coil']:
self.ejector = PulseCoilEjector(self)
elif self.config['hold_coil']:
self.ejector = HoldCoilEjector(self)

# register switch and event handlers
self._register_handlers()

def _initialize_phase_3(self):
self.config['captures_from'].ball_search.register(
self.config['ball_search_order'], self.ball_search)
if self.ejector:
self.config['captures_from'].ball_search.register(
self.config['ball_search_order'], self.ejector.ball_search)

# Register events to watch for ejects targeted at this device
for device in self.machine.ball_devices:
Expand Down Expand Up @@ -894,35 +897,6 @@ def _initialize_phase_4(self, **kwargs):
del kwargs
self._state_invalid_start()

def _fire_coil_for_search(self, full_power):
if self.config['eject_coil']:
if not full_power and self.config['eject_coil_jam_pulse']:
self.config['eject_coil'].pulse(self.config['eject_coil_jam_pulse'])
else:
self.config['eject_coil'].pulse()
return True

if self.config['hold_coil']:
self.config['hold_coil'].pulse()
return True

def ball_search(self, phase, iteration):
"""Run ball search."""
del iteration
if phase == 1:
# round 1: only idle + no ball
# only run ball search when the device is idle and contains no balls
if self._state == "idle" and self.balls == 0:
return self._fire_coil_for_search(True)
elif phase == 2:
# round 2: all devices except trough. small pulse
if 'trough' not in self.config['tags']:
return self._fire_coil_for_search(False)
else:
# round 3: all devices except trough. normal pulse
if 'trough' not in self.config['tags']:
return self._fire_coil_for_search(True)

def get_status(self, request=None): # pragma: no cover
"""Return a dictionary of current status of this ball device.
Expand Down Expand Up @@ -1143,6 +1117,11 @@ def _entrance_switch_handler(self):
self._handle_new_balls(1)
self.machine.ball_controller.trigger_ball_count()

@property
def state(self):
"""Return the device state."""
return self._state

def is_ball_count_stable(self):
"""Return if ball count is stable."""
return self._state == "idle" and self._idle_counted and not len(self._incoming_balls)
Expand Down Expand Up @@ -1252,8 +1231,7 @@ def setup_player_controlled_eject(self, balls=1, target=None):
assert balls == 1

if self.config['mechanical_eject'] or (
self.config['player_controlled_eject_event'] and (
self.config['eject_coil'] or self.config['hold_coil'])):
self.config['player_controlled_eject_event'] and self.ejector):

self._setup_or_queue_eject_to_target(target, True)

Expand Down Expand Up @@ -1447,79 +1425,23 @@ def _perform_eject(self, target, **kwargs):
callback_kwargs={'balls': 1},
state=0)

if self.config['eject_coil']:
if self.ejector:
if self.mechanical_eject_in_progress:
self.log.debug("Will not fire eject coil because of mechanical"
"eject")
else:
self._fire_eject_coil()

elif self.config['hold_coil']:
# TODO: wait for some time to allow balls to settle for
# both entrance and after a release

self._disable_hold_coil()
self.hold_release_in_progress = True

# allow timed release of single balls and reenable coil after
# release. Disable coil when device is empty
self.delay.add(name='hold_coil_release',
ms=self.config['hold_coil_release_time'],
callback=self._hold_release_done)
self.ejector.eject_one_ball()

if not self.config['ball_switches']:
# no ball_switches. we dont know when it actually leaves the device
# assume its instant
self.balls -= 1
return self._switch_state("ball_left")

def _hold_release_done(self):
self.hold_release_in_progress = False

# reenable hold coil if there are balls left
if self.balls > 0:
self._enable_hold_coil()

def _disable_hold_coil(self):
self.config['hold_coil'].disable()
if self.debug:
self.log.debug("Disabling hold coil. New "
"balls: %s.", self.balls)

def hold(self, **kwargs):
"""Event handler for hold event."""
del kwargs
# do not enable coil when we are ejecting
if self.hold_release_in_progress:
return

self._enable_hold_coil()

def _enable_hold_coil(self):
self.config['hold_coil'].enable()
if self.debug:
self.log.debug("Enabling hold coil. New "
"balls: %s.", self.balls)

def _fire_eject_coil(self):

if (self.num_eject_attempts <= 2 and
self.config['eject_coil_jam_pulse'] and
self.config['jam_switch'] and
self.machine.switch_controller.is_active(
self.config['jam_switch'].name,
ms=self.config['entrance_count_delay'])):
self.config['eject_coil'].pulse(
self.config['eject_coil_jam_pulse'])
elif self.num_eject_attempts >= 4 and self.config['eject_coil_retry_pulse']:
self.config['eject_coil'].pulse(
self.config['eject_coil_retry_pulse'])
else:
self.config['eject_coil'].pulse()

if self.debug:
self.log.debug("Firing eject coil. New "
"balls: %s.", self.balls)
# TODO: remove when migrating config to ejectors
self.ejector.hold()

def _playfield_active(self, playfield, **kwargs):
del playfield
Expand Down
22 changes: 22 additions & 0 deletions mpf/devices/ball_device/ball_device_ejector.py
@@ -0,0 +1,22 @@
"""Baseclass for ball device ejectors."""


class BallDeviceEjector:

"""Ejector for a ball device. It has to implement at least one of eject_one_ball or eject_all_balls."""

def __init__(self, ball_device):
"""Initialise ejector."""
self.ball_device = ball_device

def eject_one_ball(self):
"""Eject one ball."""
raise NotImplementedError()

def eject_all_balls(self):
"""Eject all balls."""
raise NotImplementedError()

def ball_search(self, phase, iteration):
"""Search ball in device."""
raise NotImplementedError()
66 changes: 66 additions & 0 deletions mpf/devices/ball_device/hold_coil_ejector.py
@@ -0,0 +1,66 @@
"""Hold coil ejector."""
from mpf.devices.ball_device.ball_device_ejector import BallDeviceEjector


class HoldCoilEjector(BallDeviceEjector):

"""Hold balls by enabling and releases by disabling a coil."""

def __init__(self, ball_device):
"""Initialise hold coil ejector."""
super().__init__(ball_device)
self.hold_release_in_progress = False

# handle hold_coil activation when a ball hits a switch
for switch in self.ball_device.config['hold_switches']:
self.ball_device.machine.switch_controller.add_switch_handler(
switch_name=switch.name, state=1,
ms=0,
callback=self.hold)

def eject_one_ball(self):
"""Eject one ball by disabling hold coil."""
# TODO: wait for some time to allow balls to settle for
# both entrance and after a release

self._disable_hold_coil()
self.hold_release_in_progress = True

# allow timed release of single balls and reenable coil after
# release. Disable coil when device is empty
self.ball_device.delay.add(name='hold_coil_release',
ms=self.ball_device.config['hold_coil_release_time'],
callback=self._hold_release_done)

def _disable_hold_coil(self):
self.ball_device.config['hold_coil'].disable()
if self.ball_device.debug:
self.ball_device.log.debug("Disabling hold coil. New "
"balls: %s.", self.ball_device.balls)

def hold(self, **kwargs):
"""Event handler for hold event."""
del kwargs
# do not enable coil when we are ejecting
if self.hold_release_in_progress:
return

self._enable_hold_coil()

def _enable_hold_coil(self):
self.ball_device.config['hold_coil'].enable()
if self.ball_device.debug:
self.ball_device.log.debug("Enabling hold coil. New "
"balls: %s.", self.ball_device.balls)

def _hold_release_done(self):
self.hold_release_in_progress = False

# reenable hold coil if there are balls left
if self.ball_device.balls > 0:
self._enable_hold_coil()

def ball_search(self, phase, iteration):
"""Run ball search."""
self.ball_device.config['hold_coil'].pulse()
return True
55 changes: 55 additions & 0 deletions mpf/devices/ball_device/pulse_coil_ejector.py
@@ -0,0 +1,55 @@
"""Standard pulse ejector."""
from mpf.devices.ball_device.ball_device_ejector import BallDeviceEjector


class PulseCoilEjector(BallDeviceEjector):

"""Pulse a coil to eject one ball."""

def eject_one_ball(self):
"""Pulse eject coil."""
if (self.ball_device.num_eject_attempts <= 2 and
self.ball_device.config['eject_coil_jam_pulse'] and
self.ball_device.config['jam_switch'] and
self.ball_device.machine.switch_controller.is_active(
self.ball_device.config['jam_switch'].name,
ms=self.ball_device.config['entrance_count_delay'])):
self.ball_device.config['eject_coil'].pulse(
self.ball_device.config['eject_coil_jam_pulse'])

elif self.ball_device.num_eject_attempts >= 4 and self.ball_device.config['eject_coil_retry_pulse']:
self.ball_device.config['eject_coil'].pulse(self.ball_device.config['eject_coil_retry_pulse'])

else:
self.ball_device.config['eject_coil'].pulse()

if self.ball_device.debug:
self.ball_device.log.debug("Firing eject coil. New balls: %s.", self.ball_device.balls)

def eject_all_balls(self):
"""Cannot eject all balls."""
raise NotImplementedError()

def ball_search(self, phase, iteration):
"""Run ball search."""
del iteration
if phase == 1:
# round 1: only idle + no ball
# only run ball search when the device is idle and contains no balls
if self.ball_device.state == "idle" and self.ball_device.balls == 0:
return self._fire_coil_for_search(True)
elif phase == 2:
# round 2: all devices except trough. small pulse
if 'trough' not in self.ball_device.config['tags']:
return self._fire_coil_for_search(False)
else:
# round 3: all devices except trough. normal pulse
if 'trough' not in self.ball_device.config['tags']:
return self._fire_coil_for_search(True)

def _fire_coil_for_search(self, full_power):
if not full_power and self.ball_device.config['eject_coil_jam_pulse']:
self.ball_device.config['eject_coil'].pulse(self.ball_device.config['eject_coil_jam_pulse'])
else:
self.ball_device.config['eject_coil'].pulse()
return True
2 changes: 1 addition & 1 deletion mpf/mpfconfig.yaml
Expand Up @@ -42,7 +42,7 @@ mpf:
- mpf.devices.led.Led
- mpf.devices.gi.Gi
- mpf.devices.autofire.AutofireCoil
- mpf.devices.ball_device.BallDevice
- mpf.devices.ball_device.ball_device.BallDevice
- mpf.devices.playfield.Playfield
- mpf.devices.drop_target.DropTarget
- mpf.devices.drop_target.DropTargetBank
Expand Down

0 comments on commit 9b0dcfc

Please sign in to comment.