Repository: missionpinball/mpf
Issue: playfield num_balls_requested bug with system 11 style trough

This needs investigation to see if it's a problem with all configurations or just this one.

The issue is when a ball lands in bd_trough, pf num_balls_requested goes up by one when it ejects, and then when the trough ejects to the plunger, pf num_balls_requested goes up again. When the ball gets to the pf, the num_balls_requested is decreased by 1, but the net is +1 too many for each ball launch. Over time it keeps adding up.

Config in question:

```
ball_devices:
  bd_drain:
    tags: drain
    ball_switches: s_drain
    eject_coil: c_draineject
    #exit_count_delay: 500ms
    eject_targets: bd_trough
    debug: true
    target_on_unexpected_ball: bd_trough
  bd_trough:
    tags: trough, home
    entrance_switch: s_trough_enter
    entrance_switch_full_timeout: 500ms
    eject_coil: c_Trougheject
    ball_capacity: 3
    eject_targets: bd_plunger
    eject_timeouts: 3s
    exit_count_delay: 1.5s
    #entrance_count_delay: 500ms
    debug: true
  bd_plunger:
    tags: ball_add_live
    ball_switches: s_PlungerLane
    entrance_count_delay: 500ms
    eject_coil: c_Plunger
    eject_targets: playfield
    eject_timeouts: 3s
    player_controlled_eject_event: sw_launch
    mechanical_eject: true
    debug: true
```


Changed files:
mpf/devices/ball_device.py, mpf/tests/test_GottliebTrough.py

diff --git a/mpf/devices/ball_device.py b/mpf/devices/ball_device.py
index dc033ea73..0b36137b8 100644
--- a/mpf/devices/ball_device.py
+++ b/mpf/devices/ball_device.py
@@ -368,6 +368,7 @@ class BallDevice(SystemWideDevice):
             self.eject_in_progress_target = self.config['eject_targets'][0]
 
         self.mechanical_eject_in_progress = True
+        self.num_eject_attempts += 1
         self._notify_target_of_incoming_ball(self.eject_in_progress_target)
         self._do_eject_attempt()
 
@@ -376,8 +377,11 @@ class BallDevice(SystemWideDevice):
             # We dont have balls. How can that happen?
             raise AssertionError("We dont have balls but lose one!")
         elif self.balls < balls:
-            self._cancel_incoming_ball_at_target(self.eject_in_progress_target)
+            target = self.eject_in_progress_target
+            self.eject_in_progress_target = None
+            self._cancel_incoming_ball_at_target(target)
             self._cancel_eject_confirmation()
+            self._inform_target_about_failed_confirm(target, 1, True)
 
             # Go to idle state
             return self._switch_state("idle")
@@ -1775,7 +1779,9 @@ class BallDevice(SystemWideDevice):
 
         # cancel eject confirmations
         self._cancel_eject_confirmation()
+        self._inform_target_about_failed_confirm(target, balls, retry)
 
+    def _inform_target_about_failed_confirm(self, target, balls, retry):
         self.machine.events.post('balldevice_' + self.name +
                                  '_ball_eject_failed',
                                  target=target,
diff --git a/mpf/tests/test_GottliebTrough.py b/mpf/tests/test_GottliebTrough.py
index 1f551f2a6..d24ea457e 100644
--- a/mpf/tests/test_GottliebTrough.py
+++ b/mpf/tests/test_GottliebTrough.py
@@ -108,14 +108,22 @@ class TestGottliebTrough(MpfTestCase):
         self.assertEqual('idle', self.machine.ball_devices.outhole._state)
         self.assertEqual('idle', self.machine.ball_devices.trough._state)
         self.assertEqual('ejecting', self.machine.ball_devices.plunger._state)
-        # self.assertEquals(1,
-        #                   self.machine.ball_devices.playfield.num_balls_requested)
+        self.assertEqual(0, self.machine.ball_devices.playfield.balls)
+        self.assertEqual(1, self.machine.ball_devices.playfield.num_balls_requested)
+        self.machine.switch_controller.process_switch("plunger", 0)
 
         self.advance_time_and_run(1)
         self.machine.switch_controller.process_switch("playfield", 1)
         self.machine.switch_controller.process_switch("playfield", 0)
         self.advance_time_and_run(1)
 
+        self.assertEqual('idle', self.machine.ball_devices.outhole._state)
+        self.assertEqual('idle', self.machine.ball_devices.trough._state)
+        self.assertEqual('idle', self.machine.ball_devices.plunger._state)
+
+        self.assertEqual(0, self.machine.ball_devices.playfield.num_balls_requested)
+        self.assertEqual(1, self.machine.ball_devices.playfield.balls)
+
     def test_boot_and_start_game_with_ball_in_plunger(self):
         self.machine.ball_controller.num_balls_known = 0
         self.machine.coils.outhole.pulse = MagicMock()