def initialise(self):
        """Initialise handler."""
        if self.ball_device.config['ball_switches']:
            self.counter = SwitchCounter(self.ball_device, self.ball_device.config)
        else:
            self.counter = EntranceSwitchCounter(self.ball_device, self.ball_device.config)

        self._ball_count = yield from self.counter.count_balls()
        # on start try to reorder balls if count is unstable
        if self.counter.is_count_unreliable():
            self.debug_log("BCH: Count is unstable. Trying to reorder balls.")
            yield from self.ball_device.ejector.reorder_balls()
            # recount
            self._ball_count = yield from self.counter.count_balls()

        if self._ball_count > 0:
            self._has_balls.set()
        self.ball_device.counted_balls = self._ball_count
        yield from super().initialise()
        self._count_valid.set()
示例#2
0
    def _create_ball_counters(self, queue: QueuedEvent, **kwargs):
        """Create ball counters."""
        del kwargs
        if self.config['ball_switches']:
            self.counter = SwitchCounter(self, self.config)
        else:
            self.counter = EntranceSwitchCounter(self, self.config)

        queue.wait()
        complete_future = Util.ensure_future(self._initialize_async(), loop=self.machine.clock.loop)
        complete_future.add_done_callback(lambda x: queue.clear())
示例#3
0
    def _create_ball_counters(self, **kwargs):
        del kwargs
        if self.config['ball_switches']:
            self.counter = SwitchCounter(
                self,
                self.config)  # pylint: disable-msg=redefined-variable-type
        else:
            self.counter = EntranceSwitchCounter(
                self,
                self.config)  # pylint: disable-msg=redefined-variable-type

        self.machine.clock.loop.run_until_complete(self._initialize_async())
class BallCountHandler(BallDeviceStateHandler):

    """Handles the ball count in the device."""

    def __init__(self, ball_device):
        """Initialise ball count handler."""
        super().__init__(ball_device)
        # inputs
        self._is_counting = asyncio.Lock(loop=self.machine.clock.loop)
        self._count_valid = asyncio.Event(loop=self.machine.clock.loop)
        self._revalidate = asyncio.Event(loop=self.machine.clock.loop)
        self._eject_started = asyncio.Event(loop=self.machine.clock.loop)
        self._eject_ended = asyncio.Event(loop=self.machine.clock.loop)
        self._has_balls = asyncio.Event(loop=self.machine.clock.loop)
        self._ball_count = 0
        self._ball_count_changed_futures = []
        self.counter = None  # type: PhysicalBallCounter

    def wait_for_ball_count_changed(self):
        """Wait until ball count changed."""
        future = asyncio.Future(loop=self.machine.clock.loop)
        self._ball_count_changed_futures.append(future)
        return future

    @property
    def handled_balls(self):
        """Return balls which are already handled."""
        return self._ball_count

    def _set_ball_count(self, count):
        self._ball_count = count
        # mirror variable at ball device for monitor
        self.ball_device.counted_balls = count
        if self._ball_count > 0:
            self._has_balls.set()
        else:
            self._has_balls.clear()

        self.machine.events.post("balldevice_{}_ball_count_changed".format(self.ball_device.name), balls=count)
        '''event: balldevice_(name)_ball_count_changed

        desc: The ball count for device (name) just changed.

        This event may also be called without a change in some circumstances.

        args:

        balls: The number of new balls in this device.
        '''

        for future in self._ball_count_changed_futures:
            if not future.done():
                future.set_result(count)

        # reset futures
        self._ball_count_changed_futures = []

    @asyncio.coroutine
    def initialise(self):
        """Initialise handler."""
        if self.ball_device.config['ball_switches']:
            self.counter = SwitchCounter(self.ball_device, self.ball_device.config)
        else:
            self.counter = EntranceSwitchCounter(self.ball_device, self.ball_device.config)

        self._ball_count = yield from self.counter.count_balls()
        # on start try to reorder balls if count is unstable
        if self.counter.is_count_unreliable():
            self.debug_log("BCH: Count is unstable. Trying to reorder balls.")
            yield from self.ball_device.ejector.reorder_balls()
            # recount
            self._ball_count = yield from self.counter.count_balls()

        if self._ball_count > 0:
            self._has_balls.set()
        self.ball_device.counted_balls = self._ball_count
        yield from super().initialise()
        self._count_valid.set()

    @property
    def has_ball(self) -> bool:
        """Return true if the device has at least one ball."""
        return self._ball_count > 0

    @property
    def is_full(self) -> bool:
        """Return true if the device is full."""
        return self.ball_device.config['ball_capacity'] - self._ball_count <= 0

    @asyncio.coroutine
    def wait_for_ball(self):
        """Wait until the device has a ball."""
        if self.has_ball:
            self.debug_log("We have %s balls.", self._ball_count)
            return

        self.debug_log("No ball found. Waiting for balls.")

        # wait until we have more than 0 balls
        ball_changes = Util.ensure_future(self.counter.wait_for_ball_count_changes(0),
                                          loop=self.machine.clock.loop)
        new_balls = yield from ball_changes

        # update count
        old_ball_count = self._ball_count
        self._ball_count = new_balls
        if new_balls > old_ball_count:
            self.debug_log("BCH: Found %s new balls", new_balls - old_ball_count)
            # handle new balls via incoming balls handler
            for _ in range(new_balls - old_ball_count):
                yield from self.ball_device.incoming_balls_handler.ball_arrived()
            self._set_ball_count(new_balls)

        self.debug_log("A ball arrived. Progressing.")

    @asyncio.coroutine
    # pylint: disable-msg=inconsistent-return-statements
    def wait_for_ready_to_receive(self, source):
        """Wait until this device is ready to receive a ball."""
        while True:
            free_space = self.ball_device.config['ball_capacity'] - self._ball_count
            incoming_balls = self.ball_device.incoming_balls_handler.get_num_incoming_balls()
            if free_space > incoming_balls:
                self.debug_log("Ready to receive from %s. Free space %s (Capacity: %s, Balls: %s), incoming_balls: %s",
                               source, free_space, self.ball_device.config['ball_capacity'], self._ball_count,
                               incoming_balls)

                # wait until any eject conditions have passed which would break on an incoming ball
                yield from self.ball_device.outgoing_balls_handler.wait_for_ready_to_receive()

                # wait for the counter to be ready
                yield from self.counter.wait_for_ready_to_receive()
                return True

            self.debug_log("Not ready to receive from %s. Free space %s (Capacity: %s, Balls: %s), incoming_balls: %s",
                           source, free_space, self.ball_device.config['ball_capacity'], self._ball_count,
                           incoming_balls)

            yield from self.wait_for_ball_count_changed()

    @asyncio.coroutine
    def start_eject(self, already_left=False) -> Generator[int, None, EjectTracker]:
        """Start eject."""
        yield from self.ball_device.incoming_balls_handler.start_eject()
        yield from self._is_counting.acquire()
        self._eject_started.set()
        self.debug_log("Entered eject mode.")

        eject_process = EjectTracker(self, already_left)
        if already_left:
            self._set_ball_count(self._ball_count + 1)
            yield from eject_process.will_eject()
        return eject_process

    @asyncio.coroutine
    def end_eject(self, eject_process: EjectTracker, ball_left):
        """End eject."""
        eject_process.cancel()
        self.debug_log("Exited eject mode. Eject success: %s", ball_left)
        if ball_left:
            self._set_ball_count(self._ball_count - 1)
        self._eject_started.clear()
        self._is_counting.release()
        self.ball_device.incoming_balls_handler.end_eject()

    @asyncio.coroutine
    def _run(self):
        changes = self.counter.register_change_stream()
        while True:
            # wait for ball changes
            ball_changes = Util.ensure_future(changes.get(), loop=self.machine.clock.loop)
            revalidate_future = Util.ensure_future(self._revalidate.wait(), loop=self.machine.clock.loop)
            yield from Util.first([ball_changes, revalidate_future, self._eject_started.wait()],
                                  loop=self.machine.clock.loop)
            self._revalidate.clear()

            # get lock and update count
            yield from self._is_counting.acquire()

            new_balls = yield from self.counter.count_balls()

            # try to re-order the device if count is unstable
            if self.counter.is_count_unreliable():
                self.debug_log("BCH: Count is unstable. Trying to reorder balls.")
                yield from self.ball_device.ejector.reorder_balls()
                new_balls = yield from self.counter.count_balls()

            self.debug_log("BCH: Counting. New count: %s Old count: %s", new_balls, self._ball_count)

            # when jammed do not trust other switches except the jam. keep old count
            if not self.counter.is_count_unreliable():
                # otherwise handle balls
                old_ball_count = self._ball_count
                if new_balls > old_ball_count:
                    self.debug_log("BCH: Found %s new balls", new_balls - old_ball_count)
                    self._set_ball_count(new_balls)
                    # handle new balls via incoming balls handler
                    for _ in range(new_balls - old_ball_count):
                        yield from self.ball_device.incoming_balls_handler.ball_arrived()
                elif new_balls < old_ball_count:
                    if self.ball_device.config['mechanical_eject']:
                        self._set_ball_count(new_balls)
                        yield from self.ball_device.handle_mechanial_eject_during_idle()
                    else:
                        try:
                            yield from asyncio.wait_for(self.counter.wait_for_ball_activity(),
                                                        loop=self.machine.clock.loop,
                                                        timeout=self.ball_device.config['idle_missing_ball_timeout'])
                        except asyncio.TimeoutError:
                            self.debug_log("BCH: Lost %s balls", old_ball_count - new_balls)
                            self._set_ball_count(new_balls)
                            for _ in range(old_ball_count - new_balls):
                                yield from self.ball_device.lost_idle_ball()
                        else:
                            self._revalidate.set()

            self._is_counting.release()
            self._count_valid.set()

    @asyncio.coroutine
    def wait_for_count_is_valid(self):
        """Wait until count is valid."""
        self._count_valid.clear()
        self._revalidate.set()
        # wait for ball_counter to become ready
        yield from self._count_valid.wait()

    @asyncio.coroutine
    def entrance_during_eject(self):
        """Received an entrance during eject."""
        yield from self.ball_device.incoming_balls_handler.ball_arrived()
        self._set_ball_count(self._ball_count + 1)