text: """Contains the base class for flippers."""
  from typing import List
  from typing import Optional
  
  from mpf.core.events import event_handler
  from mpf.core.platform_controller import HardwareRule, EosRuleSettings
  
  from mpf.core.device_monitor import DeviceMonitor
  from mpf.core.platform_controller import SwitchRuleSettings, DriverRuleSettings, PulseRuleSettings, HoldRuleSettings
  
  from mpf.core.system_wide_device import SystemWideDevice
  
  
  @DeviceMonitor(_enabled="enabled")
  class Flipper(SystemWideDevice):
  
      """Represents a flipper in a pinball machine. Subclass of Device.
  
      Contains several methods for actions that can be performed on this flipper,
      like :meth:`enable`, :meth:`disable`, etc.
  
      Flippers have several options, including player buttons, EOS swtiches,
      multiple coil options (pulsing, hold coils, etc.)
  
      Args:
      ----
          machine: A reference to the machine controller instance.
          name: A string of the name you'll refer to this flipper object as.
      """
  
      __slots__ = ["_enabled", "_active_rules", "_sw_flipped"]
  
      config_section = 'flippers'
      collection = 'flippers'
      class_label = 'flipper'
  
      def __init__(self, machine, name):
          """Initialise flipper."""
          super().__init__(machine, name)
  
          self._enabled = False
          self._active_rules = []     # type: List[HardwareRule]
          self._sw_flipped = False
  
      async def _initialize(self):
          await super()._initialize()
          if self.config['include_in_ball_search']:
              self.config['playfield'].ball_search.register(
                  self.config['ball_search_order'], self._ball_search, self.name)
  
      @event_handler(1)
      def event_enable(self, **kwargs):
          """Handle enable control event.
  
          To prevent multiple rules at the same time we prioritize disable > enable.
          """
          del kwargs
          self.enable()
  
      def enable(self):
          """Enable the flipper by writing the necessary hardware rules to the hardware controller.
  
          The hardware rules for coils can be kind of complex given all the
          options, so we've mapped all the options out here. We literally have
          methods to enable the various rules based on the rule letters here,
          which we've implemented below. Keeps it easy to understand. :)
  
          Note there's a platform feature saved at:
          self.machine.config['platform']['hw_enable_auto_disable']. If True, it
          means that the platform hardware rules will automatically disable a coil
          that has been enabled when the trigger switch is disabled. If False, it
          means the hardware platform needs its own rule to disable the coil when
          the switch is disabled. Methods F and G below check for that feature
          setting and will not be applied to the hardware if it's True.
  
          Two coils, using EOS switch to indicate the end of the power stroke:
          Rule  Type     Coil  Switch  Action
          A.    Enable   Main  Button  active
          D.    Enable   Hold  Button  active
          E.    Disable  Main  EOS     active
  
          One coil, using EOS switch:
          Rule  Type     Coil  Switch  Action
          A.    Enable   Main  Button  active
          H.    PWM      Main  EOS     active
  
          Two coils, not using EOS switch:
          Rule  Type     Coil  Switch  Action
          B.    Pulse    Main  Button  active
          D.    Enable   Hold  Button  active
  
          One coil, not using EOS switch:
          Rule  Type       Coil  Switch  Action
          C.    Pulse/PWM  Main  button  active
  
          Use EOS switch for safety (for platforms that support multiple switch
          rules). Note that this rule is the letter "i", not a numeral 1.
          I. Enable power if button is active and EOS is not active
          """
          # prevent duplicate enable
          if self._enabled:
              return
  
          self._enabled = True
  
          self.debug_log('Enabling flipper with config: %s', self.config)
  
          # Apply the proper hardware rules for our config
  
          if self.config['activation_switch']:
              # only add rules if we are using a switch
              if self.config['use_eos']:
                  self._enable_main_coil_eos_cutoff_rule()
              elif self.config['hold_coil']:
                  self._enable_main_coil_pulse_rule()
              else:
                  self._enable_single_coil_rule()
  
              if self.config['hold_coil']:
                  self._enable_hold_coil_rule()
  
      @event_handler(10)
      def event_disable(self, **kwargs):
          """Handle disable control event.
  
          To prevent multiple rules at the same time we prioritize disable > enable.
          """
          del kwargs
          self.disable()
  
      def disable(self):
          """Disable the flipper.
  
          This method makes it so the cabinet flipper buttons no longer control
          the flippers. Used when no game is active and when the player has
          tilted.
          """
          if not self._enabled:
              return
  
          self.debug_log("Disabling")
          for rule in self._active_rules:
              # disable all rules
              self.machine.platform_controller.clear_hw_rule(rule)
  
          if self._sw_flipped:
              # disable the coils if activated via sw_flip
              self.sw_release()
  
          self._active_rules = []
  
          self._enabled = False
  
      def _get_pulse_ms(self) -> Optional[int]:
          """Return pulse_ms."""
          pulse_ms = self.config['main_coil_overwrite'].get("pulse_ms", None)
          if self.config['power_setting_name']:
              settings_factor = self.machine.settings.get_setting_value(self.config['power_setting_name'])
              if not pulse_ms:
                  pulse_ms = self.machine.config['mpf']['default_pulse_ms']
              return int(pulse_ms * settings_factor)
  
          return pulse_ms
  
      def _get_hold_pulse_ms(self) -> Optional[int]:
          """Return pulse_ms for hold coil."""
          pulse_ms = self.config['hold_coil_overwrite'].get("pulse_ms", None)
          if self.config['power_setting_name']:
              settings_factor = self.machine.settings.get_setting_value(self.config['power_setting_name'])
              if not pulse_ms:
                  pulse_ms = self.machine.config['mpf']['default_pulse_ms']
              return int(pulse_ms * settings_factor)
  
          return pulse_ms
  
      def _get_pulse_power(self) -> Optional[float]:
          """Return pulse_power."""
          pulse_power = self.config['main_coil_overwrite'].get("pulse_power", None)
          return pulse_power
  
      def _get_hold_pulse_power(self) -> Optional[float]:
          """Return pulse_power for hold coil."""
          pulse_power = self.config['hold_coil_overwrite'].get("pulse_power", None)
          return pulse_power
  
      def _get_hold_power(self) -> Optional[float]:
          """Return hold_power."""
          hold_power = self.config['main_coil_overwrite'].get("hold_power", None)
          return hold_power
  
      def _enable_single_coil_rule(self):
          self.debug_log('Enabling single coil rule')
  
          rule = self.machine.platform_controller.set_pulse_on_hit_and_enable_and_release_rule(
              SwitchRuleSettings(switch=self.config['activation_switch'], debounce=False, invert=False),
              DriverRuleSettings(driver=self.config['main_coil'], recycle=False),
              PulseRuleSettings(duration=self._get_pulse_ms(), power=self._get_pulse_power()),
              HoldRuleSettings(power=self._get_hold_power())
          )
          self._active_rules.append(rule)
  
      def _enable_main_coil_pulse_rule(self):
          self.debug_log('Enabling main coil pulse rule')
  
          rule = self.machine.platform_controller.set_pulse_on_hit_and_release_rule(
              SwitchRuleSettings(switch=self.config['activation_switch'], debounce=False, invert=False),
              DriverRuleSettings(driver=self.config['main_coil'], recycle=False),
              PulseRuleSettings(duration=self._get_pulse_ms(), power=self._get_pulse_power())
          )
          self._active_rules.append(rule)
  
      def _enable_hold_coil_rule(self):
          self.debug_log('Enabling hold coil rule')
  
          rule = self.machine.platform_controller.set_pulse_on_hit_and_enable_and_release_rule(
              SwitchRuleSettings(switch=self.config['activation_switch'], debounce=False, invert=False),
              DriverRuleSettings(driver=self.config['hold_coil'], recycle=False),
              PulseRuleSettings(duration=self._get_hold_pulse_ms(), power=self._get_hold_pulse_power()),
              HoldRuleSettings(power=self._get_hold_power())
          )
          self._active_rules.append(rule)
  
      def _enable_main_coil_eos_cutoff_rule(self):
          if self.config['hold_coil']:
              self.debug_log('Enabling main coil EOS cutoff rule w/o hold')
              rule = self.machine.platform_controller.set_pulse_on_hit_and_release_and_disable_rule(
                  SwitchRuleSettings(switch=self.config['activation_switch'], debounce=False, invert=False),
                  SwitchRuleSettings(switch=self.config['eos_switch'], debounce=False, invert=False),
                  DriverRuleSettings(driver=self.config['main_coil'], recycle=False),
                  PulseRuleSettings(duration=self._get_hold_pulse_ms(), power=self._get_hold_pulse_power()),
                  EosRuleSettings(enable_repulse=self.config["repulse_on_eos_open"])
              )
              self._active_rules.append(rule)
          else:
              self.debug_log('Enabling main coil EOS cutoff rule w/ hold')
              rule = self.machine.platform_controller.set_pulse_on_hit_and_enable_and_release_and_disable_rule(
                  SwitchRuleSettings(switch=self.config['activation_switch'], debounce=False, invert=False),
                  SwitchRuleSettings(switch=self.config['eos_switch'], debounce=False, invert=False),
                  DriverRuleSettings(driver=self.config['main_coil'], recycle=False),
                  PulseRuleSettings(duration=self._get_hold_pulse_ms(), power=self._get_hold_pulse_power()),
                  HoldRuleSettings(power=self._get_hold_power()),
                  EosRuleSettings(enable_repulse=self.config["repulse_on_eos_open"])
              )
              self._active_rules.append(rule)
  
      @event_handler(6)
      def event_sw_flip(self, **kwargs):
          """Handle sw_flip control event."""
          del kwargs
          self.sw_flip()
  
      def sw_flip(self):
          """Activate the flipper via software as if the flipper button was pushed.
  
          This is needed because the real flipper activations are handled in
          hardware, so if you want to flip the flippers with the keyboard or OSC
          interfaces, you have to call this method.
  
          Note this method will keep this flipper enabled until you call
          sw_release().
          """
          if not self._enabled:
              return
  
          self._sw_flipped = True
  
          if self.config['hold_coil']:
              self.config['main_coil'].pulse()
              self.config['hold_coil'].enable()
          else:
              self.config['main_coil'].enable()
  
      @event_handler(5)
      def event_sw_release(self, **kwargs):
          """Handle sw_release control event."""
          del kwargs
          self.sw_release()
  
      def sw_release(self):
          """Deactivate the flipper via software as if the flipper button was released.
  
          See the documentation for sw_flip() for details.
          """
          self._sw_flipped = False
  
          # disable the flipper coil(s)
          self.config['main_coil'].disable()
  
          if self.config['hold_coil']:
              self.config['hold_coil'].disable()
  
      def _ball_search(self, phase, iteration):
          del phase
          del iteration
          self.sw_flip()
          self.machine.delay.add(self.config['ball_search_hold_time'],
                                 self.sw_release,
                                 'flipper_{}_ball_search'.format(self.name))
          return True
  