Ejemplo n.º 1
0
class WorkerBase(ABC):
    def __init__(self, args, id, last_known_state, websocket_handler,
                 walker_routemanager, devicesettings, db_wrapper, pogoWindowManager, NoOcr=True,
                 walker=None):
        # self.thread_pool = ThreadPool(processes=2)
        self._walker_routemanager = walker_routemanager
        self._route_manager_last_time = None
        self._websocket_handler = websocket_handler
        self._communicator = Communicator(
                websocket_handler, id, self, args.websocket_command_timeout)
        self._id = id
        self._applicationArgs = args
        self._last_known_state = last_known_state
        self._work_mutex = Lock()
        self.loop = None
        self.loop_started = Event()
        self.loop_tid = None
        self._async_io_looper_thread = None
        self._location_count = 0
        self._init = self._walker_routemanager.init
        self._walker = walker

        self._lastScreenshotTaken = 0
        self._stop_worker_event = Event()
        self._db_wrapper = db_wrapper
        self._redErrorCount = 0
        self._lastScreenHash = None
        self._lastScreenHashCount = 0
        self._devicesettings = devicesettings
        self._resocalc = Resocalculator
        self._screen_x = 0
        self._screen_y = 0
        self._lastStart = ""
        self._geofix_sleeptime = 0
        self._pogoWindowManager = pogoWindowManager
        self._waittime_without_delays = 0
        self._transporttype = 0
        self._not_injected_count = 0

        self.current_location = Location(0.0, 0.0)
        self.last_location = self._devicesettings.get("last_location", None)

        if self.last_location is None:
            self.last_location = Location(0.0, 0.0)

        if self._devicesettings.get('last_mode', None) is not None and \
                self._devicesettings['last_mode'] in ("raids_mitm", "mon_mitm", "iv_mitm", "raids_ocr"):
            # Reset last_location - no useless waiting delays (otherwise stop mode)
            logger.info('{}: last Mode not pokestop - reset saved location', str(self._id))
            self.last_location = Location(0.0, 0.0)

        self._devicesettings['last_mode'] = self._walker_routemanager.mode
        self.last_processed_location = Location(0.0, 0.0)
        self.workerstart = None

    def get_communicator(self):
        return self._communicator

    def get_screenshot_path(self) -> str:
        screenshot_ending: str = ".jpg"
        if self._devicesettings.get("screenshot_type", "jpeg") == "png":
            screenshot_ending = ".png"

        screenshot_filename = "screenshot_{}{}".format(str(self._id), screenshot_ending)
        return os.path.join(
                self._applicationArgs.temp_path, screenshot_filename)

    @abstractmethod
    def _pre_work_loop(self):
        """
        Work to be done before the main while true work-loop
        Start off asyncio loops etc in here
        :return:
        """
        pass

    @abstractmethod
    def _health_check(self):
        """
        Health check before a location is grabbed. Internally, a self._start_pogo call is already executed since
        that usually includes a topmost check
        :return:
        """
        pass

    @abstractmethod
    def _pre_location_update(self):
        """
        Override to run stuff like update injections settings in MITM worker
        Runs before walk/teleport to the location previously grabbed
        :return:
        """
        pass

    @abstractmethod
    def _move_to_location(self):
        """
        Location has previously been grabbed, the overriden function will be called.
        You may teleport or walk by your choosing
        Any post walk/teleport delays/sleeps have to be run in the derived, override method
        :return:
        """
        pass

    @abstractmethod
    def _post_move_location_routine(self, timestamp):
        """
        Routine called after having moved to a new location. MITM worker e.g. has to wait_for_data
        :param timestamp:
        :return:
        """

    @abstractmethod
    def _start_pogo(self):
        """
        Routine to start pogo.
        Return the state as a boolean do indicate a successful start
        :return:
        """
        pass

    @abstractmethod
    def _cleanup(self):
        """
        Cleanup any threads you started in derived classes etc
        self.stop_worker() and self.loop.stop() will be called afterwards
        :return:
        """

    @abstractmethod
    def _valid_modes(self):
        """
        Return a list of valid modes for the health checks
        :return:
        """

    def _start_asyncio_loop(self):
        self.loop = asyncio.new_event_loop()
        asyncio.set_event_loop(self.loop)
        self.loop_tid = current_thread()
        self.loop.call_soon(self.loop_started.set)
        self.loop.run_forever()

    def _add_task_to_loop(self, coro):
        f = functools.partial(self.loop.create_task, coro)
        if current_thread() == self.loop_tid:
            # We can call directly if we're not going between threads.
            return f()
        else:
            # We're in a non-event loop thread so we use a Future
            # to get the task from the event loop thread once
            # it's ready.
            return self.loop.call_soon_threadsafe(f)

    def start_worker(self):
        # async_result = self.thread_pool.apply_async(self._main_work_thread, ())
        t_main_work = Thread(target=self._main_work_thread)
        t_main_work.daemon = False
        t_main_work.start()
        # do some other stuff in the main process
        while not self._stop_worker_event.isSet():
            time.sleep(1)
        t_main_work.join()
        logger.info("Worker {} stopping gracefully", str(self._id))
        # async_result.get()
        return self._last_known_state

    def stop_worker(self):
        self._stop_worker_event.set()
        logger.warning("Worker {} stop called", str(self._id))

    def _internal_pre_work(self):
        current_thread().name = self._id

        self._work_mutex.acquire()
        try:
            self._turn_screen_on_and_start_pogo()
        except WebsocketWorkerRemovedException:
            logger.error("Timeout during init of worker {}", str(self._id))
            # no cleanup required here? TODO: signal websocket server somehow
            self._stop_worker_event.set()
            return

        # register worker  in routemanager
        logger.info("Try to register {} in Routemanager {}", str(
                self._id), str(self._walker_routemanager.name))
        self._walker_routemanager.register_worker(self._id)

        self._work_mutex.release()

        self._async_io_looper_thread = Thread(name=str(self._id) + '_asyncio_' + self._id,
                                              target=self._start_asyncio_loop)
        self._async_io_looper_thread.daemon = False
        self._async_io_looper_thread.start()

        self.loop_started.wait()
        self._pre_work_loop()

    def _internal_health_check(self):
        # check if pogo is topmost and start if necessary
        logger.debug(
                "_internal_health_check: Calling _start_pogo routine to check if pogo is topmost")
        self._work_mutex.acquire()
        logger.debug("_internal_health_check: worker lock acquired")
        logger.debug("Checking if we need to restart pogo")
        # Restart pogo every now and then...
        if self._devicesettings.get("restart_pogo", 80) > 0:
            # logger.debug("main: Current time - lastPogoRestart: {}", str(curTime - lastPogoRestart))
            # if curTime - lastPogoRestart >= (args.restart_pogo * 60):
            if self._location_count > self._devicesettings.get("restart_pogo", 80):
                logger.error(
                        "scanned " + str(self._devicesettings.get("restart_pogo", 80)) + " locations, restarting pogo")
                pogo_started = self._restart_pogo()
                self._location_count = 0
            else:
                pogo_started = self._start_pogo()
        else:
            pogo_started = self._start_pogo()
        self._work_mutex.release()
        logger.debug("_internal_health_check: worker lock released")
        return pogo_started

    def _internal_cleanup(self):
        # set the event just to make sure - in case of exceptions for example
        self._stop_worker_event.set()
        logger.info("Internal cleanup of {} started", str(self._id))
        self._cleanup()
        logger.info(
                "Internal cleanup of {} signalling end to websocketserver", str(self._id))
        self._walker_routemanager.unregister_worker(self._id)

        logger.info("Stopping Route")
        # self.stop_worker()
        if self._async_io_looper_thread is not None:
            logger.info("Stopping worker's asyncio loop")
            self.loop.call_soon_threadsafe(self.loop.stop)
            self._async_io_looper_thread.join()

        self._communicator.cleanup_websocket()
        logger.info("Internal cleanup of {} finished", str(self._id))

    def _main_work_thread(self):
        # TODO: signal websocketserver the removal
        try:
            self._internal_pre_work()
        except (InternalStopWorkerException, WebsocketWorkerRemovedException, WebsocketWorkerTimeoutException):
            logger.error(
                    "Failed initializing worker {}, connection terminated exceptionally", str(self._id))
            self._internal_cleanup()
            return

        if not check_max_walkers_reached(self._walker, self._walker_routemanager):
            logger.warning('Max. Walkers in Area {} - closing connections',
                           str(self._walker_routemanager.name))
            self._devicesettings['finished'] = True
            self._internal_cleanup()
            return

        while not self._stop_worker_event.isSet():
            try:
                # TODO: consider getting results of health checks and aborting the entire worker?
                walkercheck = self.check_walker()
                if not walkercheck:
                    self._devicesettings['finished'] = True
                    break
            except (InternalStopWorkerException, WebsocketWorkerRemovedException, WebsocketWorkerTimeoutException):
                logger.warning(
                        "Worker {} killed by walker settings", str(self._id))
                break

            try:
                # TODO: consider getting results of health checks and aborting the entire worker?
                self._internal_health_check()
                self._health_check()
            except (InternalStopWorkerException, WebsocketWorkerRemovedException, WebsocketWorkerTimeoutException):
                logger.error(
                        "Websocket connection to {} lost while running healthchecks, connection terminated exceptionally", str(self._id))
                break

            try:
                settings = self._internal_grab_next_location()
                if settings is None:
                    continue
            except (InternalStopWorkerException, WebsocketWorkerRemovedException, WebsocketWorkerTimeoutException):
                logger.warning(
                        "Worker of {} does not support mode that's to be run, connection terminated exceptionally", str(self._id))
                break

            try:
                logger.debug('Checking if new location is valid')
                valid = self._check_location_is_valid()
                if not valid:
                    break
            except (InternalStopWorkerException, WebsocketWorkerRemovedException, WebsocketWorkerTimeoutException):
                logger.warning(
                        "Worker {} get non valid coords!", str(self._id))
                break

            try:
                self._pre_location_update()
            except (InternalStopWorkerException, WebsocketWorkerRemovedException, WebsocketWorkerTimeoutException):
                logger.warning(
                        "Worker of {} stopping because of stop signal in pre_location_update, connection terminated exceptionally", str(self._id))
                break

            try:
                logger.debug('main worker {}: LastLat: {}, LastLng: {}, CurLat: {}, CurLng: {}',
                             str(
                                     self._id), self._devicesettings["last_location"].lat,
                             self._devicesettings["last_location"].lng, self.current_location.lat,
                             self.current_location.lng)
                time_snapshot, process_location = self._move_to_location()
            except (InternalStopWorkerException, WebsocketWorkerRemovedException, WebsocketWorkerTimeoutException):
                logger.warning(
                        "Worker {} failed moving to new location, stopping worker, connection terminated exceptionally", str(self._id))
                break

            if process_location:
                self._add_task_to_loop(self._update_position_file())
                self._location_count += 1
                if self._applicationArgs.last_scanned:
                    logger.debug("Seting new 'scannedlocation' in Database")
                    # self.update_scanned_location(currentLocation.lat, currentLocation.lng, curTime)
                    self._add_task_to_loop(self.update_scanned_location(
                            self.current_location.lat, self.current_location.lng, time_snapshot)
                    )

                try:
                    self._post_move_location_routine(time_snapshot)
                except (InternalStopWorkerException, WebsocketWorkerRemovedException, WebsocketWorkerTimeoutException):
                    logger.warning(
                            "Worker {} failed running post_move_location_routine, stopping worker", str(self._id))
                    break
                logger.info(
                        "Worker {} finished iteration, continuing work", str(self._id))

        self._internal_cleanup()

    async def _update_position_file(self):
        logger.debug("Updating .position file")
        if self.current_location is not None:
            with open(os.path.join(self._applicationArgs.file_path, self._id + '.position'), 'w') as outfile:
                outfile.write(str(self.current_location.lat) +
                              ", " + str(self.current_location.lng))

    async def update_scanned_location(self, latitude, longitude, timestamp):
        try:
            self._db_wrapper.set_scanned_location(
                    str(latitude), str(longitude), str(timestamp))
        except Exception as e:
            logger.error("Failed updating scanned location: {}", str(e))
            return

    def check_walker(self):
        mode = self._walker['walkertype']
        if mode == "countdown":
            logger.info("Checking walker mode 'countdown'")
            countdown = self._walker['walkervalue']
            if not countdown:
                logger.error(
                        "No Value for Mode - check your settings! Killing worker")
                return False
            if self.workerstart is None:
                self.workerstart = math.floor(time.time())
            else:
                if math.floor(time.time()) >= int(self.workerstart) + int(countdown):
                    return False
            return True
        elif mode == "timer":
            logger.debug("Checking walker mode 'timer'")
            exittime = self._walker['walkervalue']
            if not exittime or ':' not in exittime:
                logger.error(
                        "No or wrong Value for Mode - check your settings! Killing worker")
                return False
            return check_walker_value_type(exittime)
        elif mode == "round":
            logger.debug("Checking walker mode 'round'")
            rounds = self._walker['walkervalue']
            if len(rounds) == 0:
                logger.error(
                        "No Value for Mode - check your settings! Killing worker")
                return False
            processed_rounds = self._walker_routemanager.get_rounds(self._id)
            if int(processed_rounds) >= int(rounds):
                return False
            return True
        elif mode == "period":
            logger.debug("Checking walker mode 'period'")
            period = self._walker['walkervalue']
            if len(period) == 0:
                logger.error(
                        "No Value for Mode - check your settings! Killing worker")
                return False
            return check_walker_value_type(period)
        elif mode == "coords":
            exittime = self._walker['walkervalue']
            if len(exittime) > 0:
                return check_walker_value_type(exittime)
            return True
        elif mode == "idle":
            logger.debug("Checking walker mode 'idle'")
            if len(self._walker['walkervalue']) == 0:
                logger.error(
                        "Wrong Value for mode - check your settings! Killing worker")
                return False
            sleeptime = self._walker['walkervalue']
            logger.info('{} going to sleep', str(self._id))
            killpogo = False
            if check_walker_value_type(sleeptime):
                self._stop_pogo()
                killpogo = True
            while not self._stop_worker_event.isSet() and check_walker_value_type(sleeptime):
                time.sleep(1)
            logger.info('{} just woke up', str(self._id))
            if killpogo:
                self._start_pogo()
            return False
        else:
            logger.error("Unknown walker mode! Killing worker")
            return False
        return True

    def set_geofix_sleeptime(self, sleeptime):
        self._geofix_sleeptime = sleeptime
        return True

    def _internal_grab_next_location(self):
        # TODO: consider adding runWarningThreadEvent.set()
        self._last_known_state["last_location"] = self.last_location

        logger.debug("Requesting next location from routemanager")
        # requesting a location is blocking (iv_mitm will wait for a prioQ item), we really need to clean
        # the workers up...
        if int(self._geofix_sleeptime) > 0:
            logger.info('Getting a geofix position from MADMin - sleeping for {} seconds',
                        str(self._geofix_sleeptime))
            time.sleep(int(self._geofix_sleeptime))
            self._geofix_sleeptime = 0
        routemanager = self._walker_routemanager
        self.current_location = routemanager.get_next_location(self._id)
        return routemanager.settings

    def _init_routine(self):
        if self._applicationArgs.initial_restart is False:
            self._turn_screen_on_and_start_pogo()
        else:
            if not self._start_pogo():
                while not self._restart_pogo():
                    logger.warning("failed starting pogo")
                    # TODO: stop after X attempts

    def _check_location_is_valid(self):
        if self.current_location is None:
            # there are no more coords - so worker is finished successfully
            self._devicesettings['finished'] = True
            return None
        elif self.current_location is not None:
            logger.debug('Coords are valid')
            return True

    def _turn_screen_on_and_start_pogo(self):
        if not self._communicator.isScreenOn():
            self._communicator.startApp("de.grennith.rgc.remotegpscontroller")
            logger.warning("Turning screen on")
            self._communicator.turnScreenOn()
            time.sleep(self._devicesettings.get(
                    "post_turn_screen_on_delay", 2))
        # check if pogo is running and start it if necessary
        logger.info("turnScreenOnAndStartPogo: (Re-)Starting Pogo")
        self._start_pogo()

    def _check_screen_on(self):
        if not self._communicator.isScreenOn():
            self._communicator.startApp("de.grennith.rgc.remotegpscontroller")
            logger.warning("Turning screen on")
            self._communicator.turnScreenOn()
            time.sleep(self._devicesettings.get(
                    "post_turn_screen_on_delay", 2))

    def _stop_pogo(self):
        attempts = 0
        stop_result = self._communicator.stopApp("com.nianticlabs.pokemongo")
        pogoTopmost = self._communicator.isPogoTopmost()
        while pogoTopmost:
            attempts += 1
            if attempts > 10:
                return False
            stop_result = self._communicator.stopApp(
                    "com.nianticlabs.pokemongo")
            time.sleep(1)
            pogoTopmost = self._communicator.isPogoTopmost()
        return stop_result

    def _reboot(self, mitm_mapper: Optional[MitmMapper]=None):
        if not self._devicesettings.get("reboot", True):
            logger.warning("Reboot command to be issued to device but reboot is disabled. Skipping reboot")
            return True
        try:
            start_result = self._communicator.reboot()
        except WebsocketWorkerRemovedException:
            logger.error(
                    "Could not reboot due to client already having disconnected")
            start_result = False
        time.sleep(5)
        if mitm_mapper is not None:
            mitm_mapper.collect_location_stats(self._id, self.current_location, 1, time.time(), 3, 0,
                                               self._walker_routemanager.get_walker_type(), 99)
        self._db_wrapper.save_last_reboot(self._id)
        self.stop_worker()
        return start_result

    def _start_pogodroid(self):
        start_result = self._communicator.startApp("com.mad.pogodroid")
        time.sleep(5)
        return start_result

    def _stopPogoDroid(self):
        stopResult = self._communicator.stopApp("com.mad.pogodroid")
        return stopResult

    def _restart_pogo(self, clear_cache=True, mitm_mapper: Optional[MitmMapper] = None):
        successful_stop = self._stop_pogo()
        self._db_wrapper.save_last_restart(self._id)
        logger.debug("restartPogo: stop pogo resulted in {}",
                     str(successful_stop))
        if successful_stop:
            if clear_cache:
                self._communicator.clearAppCache("com.nianticlabs.pokemongo")
            time.sleep(1)
            if mitm_mapper is not None:
                mitm_mapper.collect_location_stats(self._id, self.current_location, 1, time.time(), 4, 0,
                                                   self._walker_routemanager.get_walker_type(), 99)
            return self._start_pogo()
        else:
            return False

    def _restartPogoDroid(self):
        successfulStop = self._stopPogoDroid()
        time.sleep(1)
        logger.debug(
                "restartPogoDroid: stop pogodriud resulted in {}", str(successfulStop))
        if successfulStop:
            return self._start_pogodroid()
        else:
            return False

    def _reopenRaidTab(self):
        logger.debug("_reopenRaidTab: Taking screenshot...")
        logger.debug(
                "reopenRaidTab: Attempting to retrieve screenshot before checking raidtab")
        if not self._takeScreenshot():
            logger.debug("_reopenRaidTab: Failed getting screenshot...")
            logger.error(
                    "reopenRaidTab: Failed retrieving screenshot before checking for closebutton")
            return
        logger.debug("_reopenRaidTab: Checking close except nearby...")
        pathToPass = self.get_screenshot_path()
        logger.debug("Path: {}", str(pathToPass))
        self._pogoWindowManager.check_close_except_nearby_button(
                pathToPass, self._id, self._communicator, 'True')
        logger.debug("_reopenRaidTab: Getting to raidscreen...")
        self._getToRaidscreen(3)
        time.sleep(1)

    def _get_trash_positions(self):
        logger.debug("_get_trash_positions: Get_trash_position.")
        if not self._takeScreenshot(delayBefore=self._devicesettings.get("post_screenshot_delay", 1)):
            logger.debug("_get_trash_positions: Failed getting screenshot")
            return None

        if os.path.isdir(self.get_screenshot_path()):
            logger.error(
                    "_get_trash_positions: screenshot.png is not a file/corrupted")
            return None

        logger.debug("_get_trash_positions: checking screen")
        trashes = self._pogoWindowManager.get_trash_click_positions(self.get_screenshot_path())

        return trashes

    def _takeScreenshot(self, delayAfter=0.0, delayBefore=0.0):
        logger.debug("Taking screenshot...")
        time.sleep(delayBefore)
        compareToTime = time.time() - self._lastScreenshotTaken
        logger.debug("Last screenshot taken: {}",
                     str(self._lastScreenshotTaken))

        # TODO: area settings for jpg/png and quality?
        screenshot_type: ScreenshotType = ScreenshotType.JPEG
        if self._devicesettings.get("screenshot_type", "jpeg") == "png":
            screenshot_type = ScreenshotType.PNG

        screenshot_quality: int = self._devicesettings.get("screenshot_quality", 80)

        take_screenshot = self._communicator.get_screenshot(self.get_screenshot_path(),
                                                            screenshot_quality, screenshot_type)

        if self._lastScreenshotTaken and compareToTime < 0.5:
            logger.debug(
                    "takeScreenshot: screenshot taken recently, returning immediately")
            logger.debug("Screenshot taken recently, skipping")
            return True

        elif not take_screenshot:
            logger.error("takeScreenshot: Failed retrieving screenshot")
            logger.debug("Failed retrieving screenshot")
            return False
        else:
            logger.debug("Success retrieving screenshot")
            self._lastScreenshotTaken = time.time()
            time.sleep(delayAfter)
            return True

    def _checkPogoFreeze(self):
        logger.debug("Checking if pogo froze")
        if not self._takeScreenshot():
            logger.debug("_checkPogoFreeze: failed retrieving screenshot")
            return
        from utils.image_utils import getImageHash
        screenHash = getImageHash(os.path.join(self.get_screenshot_path()))
        logger.debug("checkPogoFreeze: Old Hash: {}",
                     str(self._lastScreenHash))
        logger.debug("checkPogoFreeze: New Hash: {}", str(screenHash))
        if hamming_dist(str(self._lastScreenHash), str(screenHash)) < 4 and str(self._lastScreenHash) != '0':
            logger.debug(
                    "checkPogoFreeze: New und old Screenshoot are the same - no processing")
            self._lastScreenHashCount += 1
            logger.debug("checkPogoFreeze: Same Screen Count: " +
                         str(self._lastScreenHashCount))
            if self._lastScreenHashCount >= 100:
                self._lastScreenHashCount = 0
                self._restart_pogo()
        else:
            self._lastScreenHash = screenHash
            self._lastScreenHashCount = 0

            logger.debug("_checkPogoFreeze: done")

    def _check_pogo_main_screen(self, maxAttempts, again=False):
        logger.debug(
                "_check_pogo_main_screen: Trying to get to the Mainscreen with {} max attempts...", str(maxAttempts))
        pogoTopmost = self._communicator.isPogoTopmost()
        if not pogoTopmost:
            return False

        if not self._takeScreenshot(delayBefore=self._devicesettings.get("post_screenshot_delay", 1)):
            if again:
                logger.error(
                        "_check_pogo_main_screen: failed getting a screenshot again")
                return False
        attempts = 0

        screenshot_path = self.get_screenshot_path()
        if os.path.isdir(screenshot_path):
            logger.error(
                    "_check_pogo_main_screen: screenshot.png/.jpg is not a file/corrupted")
            return False

        logger.debug("_check_pogo_main_screen: checking mainscreen")
        buttoncheck = self._pogoWindowManager.look_for_button(screenshot_path, 2.20, 3.01, self._communicator)
        if buttoncheck:
            logger.debug('Found button on screen')
            self._takeScreenshot(delayBefore=self._devicesettings.get(
                    "post_screenshot_delay", 1))
        while not self._pogoWindowManager.check_pogo_mainscreen(screenshot_path, self._id):
            logger.error("_check_pogo_main_screen: not on Mainscreen...")
            if attempts > maxAttempts:
                # could not reach raidtab in given maxAttempts
                logger.error(
                        "_check_pogo_main_screen: Could not get to Mainscreen within {} attempts", str(maxAttempts))
                return False

            # not using continue since we need to get a screen before the next round...
            found = self._pogoWindowManager.look_for_button(screenshot_path, 2.20, 3.01, self._communicator)
            if found:
                logger.debug("_check_pogo_main_screen: Found button (small)")

            if not found and self._pogoWindowManager.check_close_except_nearby_button(
                    self.get_screenshot_path(), self._id,
                    self._communicator, close_raid=True):
                logger.debug("_check_pogo_main_screen: Found (X) button (except nearby)")
                found = True

            if not found and self._pogoWindowManager.look_for_button(screenshot_path, 1.05, 2.20, self._communicator):
                logger.debug("_check_pogo_main_screen: Found button (big)")
                found = True

            logger.debug("_check_pogo_main_screen: Previous checks found popups: {}", str(found))

            self._takeScreenshot(delayBefore=self._devicesettings.get(
                    "post_screenshot_delay", 1))

            attempts += 1
        logger.debug("_check_pogo_main_screen: done")
        return True

    def _checkPogoButton(self):
        logger.debug("checkPogoButton: Trying to find buttons")
        pogoTopmost = self._communicator.isPogoTopmost()
        if not pogoTopmost:
            return False

        if not self._takeScreenshot(delayBefore=self._devicesettings.get("post_screenshot_delay", 1)):
            # TODO: again?
            # if again:
            #     logger.error("checkPogoButton: failed getting a screenshot again")
            #     return False
            # TODO: throw?
            logger.debug("checkPogoButton: Failed getting screenshot")
            return False
        attempts = 0

        if os.path.isdir(self.get_screenshot_path()):
            logger.error("checkPogoButton: screenshot.png is not a file/corrupted")
            return False

        logger.debug("checkPogoButton: checking for buttons")
        found = self._pogoWindowManager.look_for_button(self.get_screenshot_path(), 2.20, 3.01, self._communicator)
        if found:
            time.sleep(1)
            logger.debug("checkPogoButton: Found button (small)")
            logger.debug("checkPogoButton: done")
            return True
        logger.debug("checkPogoButton: done")
        return False

    def _checkPogoClose(self):
        logger.debug("checkPogoClose: Trying to find closeX")
        pogoTopmost = self._communicator.isPogoTopmost()
        if not pogoTopmost:
            return False

        if not self._takeScreenshot(delayBefore=self._devicesettings.get("post_screenshot_delay", 1)):
            # TODO: go again?
            # if again:
            #     logger.error("checkPogoClose: failed getting a screenshot again")
            #     return False
            # TODO: consider throwing?
            logger.debug("checkPogoClose: Could not get screenshot")
            return False
        attempts = 0

        if os.path.isdir(self.get_screenshot_path()):
            logger.error("checkPogoClose: screenshot.png is not a file/corrupted")
            return False

        logger.debug("checkPogoClose: checking for CloseX")
        found = self._pogoWindowManager.check_close_except_nearby_button(self.get_screenshot_path() , self._id,
                                                                         self._communicator)
        if found:
            time.sleep(1)
            logger.debug("checkPogoClose: Found (X) button (except nearby)")
            logger.debug("checkPogoClose: done")
            return True
        logger.debug("checkPogoClose: done")
        return False

    def _getToRaidscreen(self, maxAttempts, again=False):
        # check for any popups (including post login OK)
        logger.debug(
                "getToRaidscreen: Trying to get to the raidscreen with {} max attempts...", str(maxAttempts))
        pogoTopmost = self._communicator.isPogoTopmost()
        if not pogoTopmost:
            return False

        self._checkPogoFreeze()
        if not self._takeScreenshot(delayBefore=self._devicesettings.get("post_screenshot_delay", 1)):
            if again:
                logger.error(
                        "getToRaidscreen: failed getting a screenshot again")
                return False
            self._getToRaidscreen(maxAttempts, True)
            logger.debug("getToRaidscreen: Got screenshot, checking GPS")
        attempts = 0

        if os.path.isdir(self.get_screenshot_path()):
            logger.error(
                    "getToRaidscreen: screenshot.png is not a file/corrupted")
            return False

        # TODO: replace self._id with device ID
        while self._pogoWindowManager.is_gps_signal_lost(self.get_screenshot_path(), self._id):
            logger.debug("getToRaidscreen: GPS signal lost")
            time.sleep(1)
            self._takeScreenshot()
            logger.warning("getToRaidscreen: GPS signal error")
            self._redErrorCount += 1
            if self._redErrorCount > 3:
                logger.error(
                        "getToRaidscreen: Red error multiple times in a row, restarting")
                self._redErrorCount = 0
                self._restart_pogo()
                return False
        self._redErrorCount = 0
        logger.debug("getToRaidscreen: checking raidscreen")
        while not self._pogoWindowManager.check_raidscreen(self.get_screenshot_path(), self._id,
                                                           self._communicator):
            logger.debug("getToRaidscreen: not on raidscreen...")
            if attempts > maxAttempts:
                # could not reach raidtab in given maxAttempts
                logger.error(
                        "getToRaidscreen: Could not get to raidtab within {} attempts", str(maxAttempts))
                return False
            self._checkPogoFreeze()
            # not using continue since we need to get a screen before the next round...
            found = self._pogoWindowManager.look_for_button(self.get_screenshot_path(), 2.20, 3.01, self._communicator)
            if found:
                logger.debug("getToRaidscreen: Found button (small)")

            if not found and self._pogoWindowManager.check_close_except_nearby_button(self.get_screenshot_path(),
                                                                                      self._id, self._communicator):
                logger.debug(
                        "getToRaidscreen: Found (X) button (except nearby)")
                found = True

            if not found and self._pogoWindowManager.look_for_button(self.get_screenshot_path(), 1.05, 2.20,
                                                                     self._communicator):
                logger.debug("getToRaidscreen: Found button (big)")
                found = True

            logger.debug(
                    "getToRaidscreen: Previous checks found popups: {}", str(found))
            if not found:
                logger.debug(
                        "getToRaidscreen: Previous checks found nothing. Checking nearby open")
                if self._pogoWindowManager.check_nearby(self.get_screenshot_path(), self._id,
                                                        self._communicator):
                    return self._takeScreenshot(delayBefore=self._devicesettings.get("post_screenshot_delay", 1))

            if not self._takeScreenshot(delayBefore=self._devicesettings.get("post_screenshot_delay", 1)):
                return False

            attempts += 1
        logger.debug("getToRaidscreen: done")
        return True

    def _get_screen_size(self):
        screen = self._communicator.getscreensize().split(' ')
        self._screen_x = screen[0]
        self._screen_y = screen[1]
        x_offset = self._devicesettings.get("screenshot_x_offset", 0)
        y_offset = self._devicesettings.get("screenshot_y_offset", 0)
        logger.debug('Get Screensize of {}: X: {}, Y: {}, X-Offset: {}, Y-Offset: {}', str(
                self._id), str(self._screen_x), str(self._screen_y), str(x_offset), str(y_offset))
        self._resocalc.get_x_y_ratio(
                self, self._screen_x, self._screen_y, x_offset, y_offset)
Ejemplo n.º 2
0
class WorkerBase(ABC):
    def __init__(self,
                 args,
                 id,
                 last_known_state,
                 websocket_handler,
                 route_manager_daytime,
                 route_manager_nighttime,
                 devicesettings,
                 db_wrapper,
                 timer,
                 pogoWindowManager,
                 NoOcr=True):
        # self.thread_pool = ThreadPool(processes=2)
        self._route_manager_daytime = route_manager_daytime
        self._route_manager_nighttime = route_manager_nighttime
        self._route_manager_last_time = None
        self._websocket_handler = websocket_handler
        self._communicator = Communicator(websocket_handler, id,
                                          args.websocket_command_timeout)
        self._id = id
        self._applicationArgs = args
        self._last_known_state = last_known_state
        self._work_mutex = Lock()
        self.loop = None
        self.loop_started = Event()
        self.loop_tid = None
        self._async_io_looper_thread = None
        self._location_count = 0
        self._timer = timer

        self._lastScreenshotTaken = 0
        self._stop_worker_event = Event()
        self._db_wrapper = db_wrapper
        self._redErrorCount = 0
        self._lastScreenHash = None
        self._lastScreenHashCount = 0
        self._devicesettings = devicesettings
        self._resocalc = Resocalculator
        self._screen_x = 0
        self._screen_y = 0
        self._lastStart = ""
        self._pogoWindowManager = pogoWindowManager

        self.current_location = self._last_known_state.get(
            "last_location", None)
        if self.current_location is None:
            self.current_location = Location(0.0, 0.0)
        self.last_location = Location(0.0, 0.0)
        self.last_processed_location = Location(0.0, 0.0)

    @abstractmethod
    def _pre_work_loop(self):
        """
        Work to be done before the main while true work-loop
        Start off asyncio loops etc in here
        :return:
        """
        pass

    @abstractmethod
    def _health_check(self):
        """
        Health check before a location is grabbed. Internally, a self._start_pogo call is already executed since
        that usually includes a topmost check
        :return:
        """
        pass

    @abstractmethod
    def _pre_location_update(self):
        """
        Override to run stuff like update injections settings in MITM worker
        Runs before walk/teleport to the location previously grabbed
        :return:
        """
        pass

    @abstractmethod
    def _move_to_location(self):
        """
        Location has previously been grabbed, the overriden function will be called.
        You may teleport or walk by your choosing
        Any post walk/teleport delays/sleeps have to be run in the derived, override method
        :return:
        """
        pass

    @abstractmethod
    def _post_move_location_routine(self, timestamp):
        """
        Routine called after having moved to a new location. MITM worker e.g. has to wait_for_data
        :param timestamp:
        :return:
        """

    @abstractmethod
    def _start_pogo(self):
        """
        Routine to start pogo.
        Return the state as a boolean do indicate a successful start
        :return:
        """
        pass

    @abstractmethod
    def _cleanup(self):
        """
        Cleanup any threads you started in derived classes etc
        self.stop_worker() and self.loop.stop() will be called afterwards
        :return:
        """

    @abstractmethod
    def _valid_modes(self):
        """
        Return a list of valid modes for the health checks
        :return:
        """

    def _start_asyncio_loop(self):
        self.loop = asyncio.new_event_loop()
        asyncio.set_event_loop(self.loop)
        self.loop_tid = current_thread()
        self.loop.call_soon(self.loop_started.set)
        self.loop.run_forever()

    def _add_task_to_loop(self, coro):
        f = functools.partial(self.loop.create_task, coro)
        if current_thread() == self.loop_tid:
            return f(
            )  # We can call directly if we're not going between threads.
        else:
            # We're in a non-event loop thread so we use a Future
            # to get the task from the event loop thread once
            # it's ready.
            return self.loop.call_soon_threadsafe(f)

    def start_worker(self):
        # async_result = self.thread_pool.apply_async(self._main_work_thread, ())
        t_main_work = Thread(target=self._main_work_thread)
        t_main_work.daemon = False
        t_main_work.start()
        # do some other stuff in the main process
        while not self._stop_worker_event.isSet():
            time.sleep(1)
        t_main_work.join()
        log.info("Worker %s stopping gracefully" % str(self._id))
        # async_result.get()
        return self._last_known_state

    def stop_worker(self):
        self._stop_worker_event.set()
        log.warning("Worker %s stop called" % str(self._id))

    def _internal_pre_work(self):
        current_thread().name = self._id

        self._work_mutex.acquire()
        try:
            self._turn_screen_on_and_start_pogo()
        except WebsocketWorkerRemovedException:
            log.error("Timeout during init of worker %s" % str(self._id))
            # no cleanup required here? TODO: signal websocket server somehow
            self._stop_worker_event.set()
            return
        self._work_mutex.release()

        self._async_io_looper_thread = Thread(name=str(self._id) +
                                              '_asyncio_' + self._id,
                                              target=self._start_asyncio_loop)
        self._async_io_looper_thread.daemon = False
        self._async_io_looper_thread.start()

        self.loop_started.wait()
        self._pre_work_loop()

    def _internal_health_check(self):
        # check if pogo is topmost and start if necessary
        log.debug(
            "_internal_health_check: Calling _start_pogo routine to check if pogo is topmost"
        )
        self._work_mutex.acquire()
        log.debug("_internal_health_check: worker lock acquired")
        log.debug("Checking if we need to restart pogo")
        # Restart pogo every now and then...
        if self._devicesettings.get("restart_pogo", 80) > 0:
            # log.debug("main: Current time - lastPogoRestart: %s" % str(curTime - lastPogoRestart))
            # if curTime - lastPogoRestart >= (args.restart_pogo * 60):
            if self._location_count > self._devicesettings.get(
                    "restart_pogo", 80):
                log.error("scanned " +
                          str(self._devicesettings.get("restart_pogo", 80)) +
                          " locations, restarting pogo")
                pogo_started = self._restart_pogo()
                self._location_count = 0
            else:
                pogo_started = self._start_pogo()
        else:
            pogo_started = self._start_pogo()
        self._work_mutex.release()
        log.debug("_internal_health_check: worker lock released")
        return pogo_started

    def _internal_cleanup(self):
        # set the event just to make sure - in case of exceptions for example
        self._stop_worker_event.set()
        log.info("Internal cleanup of %s started" % str(self._id))
        self._cleanup()
        log.info("Internal cleanup of %s signalling end to websocketserver" %
                 str(self._id))
        self._route_manager_daytime.unregister_worker(self._id)
        if self._route_manager_nighttime is not None:
            self._route_manager_nighttime.unregister_worker(self._id)

        log.info("Stopping Route")
        # self.stop_worker()
        if self._async_io_looper_thread is not None:
            log.info("Stopping worker's asyncio loop")
            self.loop.call_soon_threadsafe(self.loop.stop)
            self._async_io_looper_thread.join()
        if self._timer is not None:
            log.info("Stopping switch timer")
            self._timer.stop_switch()
        self._communicator.cleanup_websocket(self)
        log.info("Internal cleanup of %s finished" % str(self._id))

    def _main_work_thread(self):
        # TODO: signal websocketserver the removal
        try:
            self._internal_pre_work()
        except (InternalStopWorkerException, WebsocketWorkerRemovedException, WebsocketWorkerTimeoutException) \
                as e:
            log.error(
                "Failed initializing worker %s, connection terminated exceptionally"
                % str(self._id))
            return

        while not self._stop_worker_event.isSet():
            while self._timer.get_switch(
            ) and self._route_manager_nighttime is None:
                time.sleep(1)
            # check if stop_worker_event is set again since sleep may have taken ages ;)
            if self._stop_worker_event.is_set():
                break

            try:
                # TODO: consider getting results of health checks and aborting the entire worker?
                self._internal_health_check()
                self._health_check()
            except (InternalStopWorkerException, WebsocketWorkerRemovedException, WebsocketWorkerTimeoutException) \
                    as e:
                log.error(
                    "Websocket connection to %s lost while running healthchecks, "
                    "connection terminated exceptionally" % str(self._id))
                break

            try:
                settings = self._internal_grab_next_location()
                if settings is None and self._timer.get_switch():
                    continue
            except (InternalStopWorkerException, WebsocketWorkerRemovedException, WebsocketWorkerTimeoutException) \
                    as e:
                log.warning(
                    "Worker of %s does not support mode that's to be run, "
                    "connection terminated exceptionally" % str(self._id))
                break

            try:
                log.debug('Checking if new location is valid')
                valid = self._check_location_is_valid()
                if not valid:
                    break
            except (InternalStopWorkerException, WebsocketWorkerRemovedException, WebsocketWorkerTimeoutException) \
                    as e:
                log.warning("Worker %s get non valid coords!" % str(self._id))
                break

            try:
                self._pre_location_update()
            except (InternalStopWorkerException, WebsocketWorkerRemovedException, WebsocketWorkerTimeoutException) \
                    as e:
                log.warning(
                    "Worker of %s stopping because of stop signal in pre_location_update, "
                    "connection terminated exceptionally" % str(self._id))
                break

            try:
                log.debug(
                    'main worker %s: LastLat: %s, LastLng: %s, CurLat: %s, CurLng: %s'
                    % (str(self._id), self.last_location.lat,
                       self.last_location.lng, self.current_location.lat,
                       self.current_location.lng))
                time_snapshot, process_location = self._move_to_location()
            except (InternalStopWorkerException, WebsocketWorkerRemovedException, WebsocketWorkerTimeoutException) \
                    as e:
                log.warning(
                    "Worker %s failed moving to new location, stopping worker, "
                    "connection terminated exceptionally" % str(self._id))
                break

            if process_location:
                self._add_task_to_loop(self._update_position_file())
                self._location_count += 1
                if self._applicationArgs.last_scanned:
                    log.info('main: Set new scannedlocation in Database')
                    # self.update_scanned_location(currentLocation.lat, currentLocation.lng, curTime)
                    self._add_task_to_loop(
                        self.update_scanned_location(self.current_location.lat,
                                                     self.current_location.lng,
                                                     time_snapshot))

                try:
                    self._post_move_location_routine(time_snapshot)
                except (InternalStopWorkerException, WebsocketWorkerRemovedException, WebsocketWorkerTimeoutException) \
                    as e:
                    log.warning(
                        "Worker %s failed running post_move_location_routine, stopping worker"
                        % str(self._id))
                    break
                log.info("Worker %s finished iteration, continuing work" %
                         str(self._id))

        self._internal_cleanup()

    async def _update_position_file(self):
        log.debug("Updating .position file")
        if self.current_location is not None:
            with open(self._id + '.position', 'w') as outfile:
                outfile.write(
                    str(self.current_location.lat) + ", " +
                    str(self.current_location.lng))

    async def update_scanned_location(self, latitude, longitude, timestamp):
        try:
            self._db_wrapper.set_scanned_location(str(latitude),
                                                  str(longitude),
                                                  str(timestamp))
        except Exception as e:
            log.error("Failed updating scanned location: %s" % str(e))
            return

    def _get_currently_valid_routemanager(self):
        valid_modes = self._valid_modes()
        switch_mode = self._timer.get_switch()
        if (switch_mode and self._route_manager_nighttime is not None
                and self._route_manager_nighttime.mode in valid_modes):
            if self._route_manager_last_time != self._route_manager_nighttime:
                self._route_manager_daytime.unregister_worker(self._id)
                # TODO: check if result is positive/negative?
                self._route_manager_nighttime.register_worker(self._id)
                self._route_manager_last_time = self._route_manager_nighttime
            return self._route_manager_nighttime
        elif switch_mode is True and self._route_manager_nighttime is None:
            if self._route_manager_last_time is not None:
                self._route_manager_daytime.unregister_worker(self._id)
                self._route_manager_last_time = None
            return None
        elif not switch_mode and self._route_manager_daytime.mode in valid_modes:
            if self._route_manager_last_time != self._route_manager_daytime:
                if self._route_manager_nighttime is not None:
                    self._route_manager_nighttime.unregister_worker(self._id)
                self._route_manager_daytime.register_worker(self._id)
                self._route_manager_last_time = self._route_manager_daytime
            return self._route_manager_daytime
        else:
            # log.fatal("Raising internal worker exception")
            raise InternalStopWorkerException

    def _internal_grab_next_location(self):
        # TODO: consider adding runWarningThreadEvent.set()
        self.last_location = self.current_location
        self._last_known_state["last_location"] = self.last_location

        log.debug("Requesting next location from routemanager")
        # requesting a location is blocking (iv_mitm will wait for a prioQ item), we really need to clean
        # the workers up...
        routemanager = self._get_currently_valid_routemanager()
        if routemanager is None:
            return None
        else:
            self.current_location = routemanager.get_next_location()
            return routemanager.settings

    def _init_routine(self):
        if self._applicationArgs.initial_restart is False:
            self._turn_screen_on_and_start_pogo()
        else:
            if not self._start_pogo():
                while not self._restart_pogo():
                    log.warning("failed starting pogo")
                    # TODO: stop after X attempts

    def _check_location_is_valid(self):
        if self.current_location is None:
            log.info('Current Location is None')
            while self._timer.get_switch():
                log.info('Sleeping - Route is finished')
                time.sleep(30)
        elif self.current_location is not None:
            log.debug('Coords are valid')
            return True

    def _turn_screen_on_and_start_pogo(self):
        if not self._communicator.isScreenOn():
            self._communicator.startApp("de.grennith.rgc.remotegpscontroller")
            log.warning("Turning screen on")
            self._communicator.turnScreenOn()
            time.sleep(self._devicesettings.get("post_turn_screen_on_delay",
                                                2))
        # check if pogo is running and start it if necessary
        log.warning("turnScreenOnAndStartPogo: (Re-)Starting Pogo")
        self._start_pogo()

    def _check_screen_on(self):
        if not self._communicator.isScreenOn():
            self._communicator.startApp("de.grennith.rgc.remotegpscontroller")
            log.warning("Turning screen on")
            self._communicator.turnScreenOn()
            time.sleep(self._devicesettings.get("post_turn_screen_on_delay",
                                                2))

    def _stop_pogo(self):
        attempts = 0
        stop_result = self._communicator.stopApp("com.nianticlabs.pokemongo")
        pogoTopmost = self._communicator.isPogoTopmost()
        while pogoTopmost:
            attempts += 1
            if attempts > 10:
                return False
            stop_result = self._communicator.stopApp(
                "com.nianticlabs.pokemongo")
            time.sleep(1)
            pogoTopmost = self._communicator.isPogoTopmost()
        return stop_result

    def _reboot(self):
        try:
            start_result = self._communicator.reboot()
        except WebsocketWorkerRemovedException as e:
            log.error(
                "Could not reboot due to client already having disconnected")
            start_result = False
        time.sleep(5)
        self._db_wrapper.save_last_reboot(self._id)
        self.stop_worker()
        return start_result

    def _start_pogodroid(self):
        start_result = self._communicator.startApp("com.mad.pogodroid")
        time.sleep(5)
        return start_result

    def _stopPogoDroid(self):
        stopResult = self._communicator.stopApp("com.mad.pogodroid")
        return stopResult

    def _restart_pogo(self, clear_cache=True):
        successful_stop = self._stop_pogo()
        self._db_wrapper.save_last_restart(self._id)
        log.debug("restartPogo: stop pogo resulted in %s" %
                  str(successful_stop))
        if successful_stop:
            if clear_cache:
                self._communicator.clearAppCache("com.nianticlabs.pokemongo")
            time.sleep(1)
            return self._start_pogo()
        else:
            return False

    def _restartPogoDroid(self):
        successfulStop = self._stopPogoDroid()
        time.sleep(1)
        log.debug("restartPogoDroid: stop pogodriud resulted in %s" %
                  str(successfulStop))
        if successfulStop:
            return self._start_pogodroid()
        else:
            return False

    def _reopenRaidTab(self):
        log.debug("_reopenRaidTab: Taking screenshot...")
        log.info(
            "reopenRaidTab: Attempting to retrieve screenshot before checking raidtab"
        )
        if not self._takeScreenshot():
            log.debug("_reopenRaidTab: Failed getting screenshot...")
            log.error(
                "reopenRaidTab: Failed retrieving screenshot before checking for closebutton"
            )
            return
        log.debug("_reopenRaidTab: Checking close except nearby...")
        pathToPass = os.path.join(self._applicationArgs.temp_path,
                                  'screenshot%s.png' % str(self._id))
        log.debug("Path: %s" % str(pathToPass))
        self._pogoWindowManager.checkCloseExceptNearbyButton(
            pathToPass, self._id, self._communicator, 'True')
        log.debug("_reopenRaidTab: Getting to raidscreen...")
        self._getToRaidscreen(3)
        time.sleep(1)

    def _takeScreenshot(self, delayAfter=0.0, delayBefore=0.0):
        log.debug("Taking screenshot...")
        time.sleep(delayBefore)
        compareToTime = time.time() - self._lastScreenshotTaken
        log.debug("Last screenshot taken: %s" % str(self._lastScreenshotTaken))

        if self._applicationArgs.use_media_projection:
            take_screenshot = self._communicator.getScreenshot(
                os.path.join(self._applicationArgs.temp_path,
                             'screenshot%s.png' % str(self._id)))
        else:
            take_screenshot = self._communicator.get_screenshot_single(
                os.path.join(self._applicationArgs.temp_path,
                             'screenshot%s.png' % str(self._id)))

        if self._lastScreenshotTaken and compareToTime < 0.5:
            log.debug(
                "takeScreenshot: screenshot taken recently, returning immediately"
            )
            log.debug("Screenshot taken recently, skipping")
            return True
        # TODO: screenshot.png needs identifier in name
        elif not take_screenshot:
            log.error("takeScreenshot: Failed retrieving screenshot")
            log.debug("Failed retrieving screenshot")
            return False
        else:
            log.debug("Success retrieving screenshot")
            self._lastScreenshotTaken = time.time()
            time.sleep(delayAfter)
            return True

    def _checkPogoFreeze(self):
        log.debug("Checking if pogo froze")
        if not self._takeScreenshot():
            log.debug("_checkPogoFreeze: failed retrieving screenshot")
            return
        from utils.image_utils import getImageHash
        screenHash = getImageHash(
            os.path.join(self._applicationArgs.temp_path,
                         'screenshot%s.png' % str(self._id)))
        log.debug("checkPogoFreeze: Old Hash: " + str(self._lastScreenHash))
        log.debug("checkPogoFreeze: New Hash: " + str(screenHash))
        if hamming_dist(str(self._lastScreenHash),
                        str(screenHash)) < 4 and str(
                            self._lastScreenHash) != '0':
            log.debug(
                "checkPogoFreeze: New und old Screenshoot are the same - no processing"
            )
            self._lastScreenHashCount += 1
            log.debug("checkPogoFreeze: Same Screen Count: " +
                      str(self._lastScreenHashCount))
            if self._lastScreenHashCount >= 100:
                self._lastScreenHashCount = 0
                self._restart_pogo()
        else:
            self._lastScreenHash = screenHash
            self._lastScreenHashCount = 0

            log.debug("_checkPogoFreeze: done")

    def _check_pogo_main_screen(self, maxAttempts, again=False):
        log.debug(
            "_check_pogo_main_screen: Trying to get to the Mainscreen with %s max attempts..."
            % str(maxAttempts))
        pogoTopmost = self._communicator.isPogoTopmost()
        if not pogoTopmost:
            return False

        self._checkPogoFreeze()
        if not self._takeScreenshot(
                delayBefore=self._applicationArgs.post_screenshot_delay):
            if again:
                log.error(
                    "_check_pogo_main_screen: failed getting a screenshot again"
                )
                return False
        attempts = 0

        if os.path.isdir(
                os.path.join(self._applicationArgs.temp_path,
                             'screenshot%s.png' % str(self._id))):
            log.error(
                "_check_pogo_main_screen: screenshot.png is not a file/corrupted"
            )
            return False

        log.info("_check_pogo_main_screen: checking mainscreen")
        buttoncheck = self._pogoWindowManager.lookForButton(
            os.path.join(self._applicationArgs.temp_path,
                         'screenshot%s.png' % str(self._id)), 2.20, 3.01,
            self._communicator)
        if buttoncheck:
            log.info('Found button on screen')
            self._takeScreenshot(
                delayBefore=self._applicationArgs.post_screenshot_delay)
        while not self._pogoWindowManager.checkpogomainscreen(
                os.path.join(self._applicationArgs.temp_path,
                             'screenshot%s.png' % str(self._id)), self._id):
            log.error("_check_pogo_main_screen: not on Mainscreen...")
            if attempts > maxAttempts:
                # could not reach raidtab in given maxAttempts
                log.error(
                    "_check_pogo_main_screen: Could not get to Mainscreen within %s attempts"
                    % str(maxAttempts))
                return False
            self._checkPogoFreeze()
            # not using continue since we need to get a screen before the next round...
            found = self._pogoWindowManager.lookForButton(
                os.path.join(self._applicationArgs.temp_path,
                             'screenshot%s.png' % str(self._id)), 2.20, 3.01,
                self._communicator)
            if found:
                log.info("_check_pogo_main_screen: Found button (small)")

            if not found and self._pogoWindowManager.checkCloseExceptNearbyButton(
                    os.path.join(self._applicationArgs.temp_path,
                                 'screenshot%s.png' % str(self._id)),
                    self._id,
                    self._communicator,
                    closeraid=True):
                log.info(
                    "_check_pogo_main_screen: Found (X) button (except nearby)"
                )
                found = True

            if not found and self._pogoWindowManager.lookForButton(
                    os.path.join(self._applicationArgs.temp_path,
                                 'screenshot%s.png' % str(self._id)), 1.05,
                    2.20, self._communicator):
                log.info("_check_pogo_main_screen: Found button (big)")
                found = True

            log.info(
                "_check_pogo_main_screen: Previous checks found popups: %s" %
                str(found))
            if not found:
                self._takeScreenshot()

            attempts += 1
        log.info("_check_pogo_main_screen: done")
        return True

    def _checkPogoButton(self):
        log.debug("checkPogoButton: Trying to find buttons")
        pogoTopmost = self._communicator.isPogoTopmost()
        if not pogoTopmost:
            return False

        self._checkPogoFreeze()
        if not self._takeScreenshot(
                delayBefore=self._applicationArgs.post_screenshot_delay):
            # TODO: again?
            # if again:
            #     log.error("checkPogoButton: failed getting a screenshot again")
            #     return False
            # TODO: throw?
            log.debug("checkPogoButton: Failed getting screenshot")
            return False
        attempts = 0

        if os.path.isdir(
                os.path.join(self._applicationArgs.temp_path,
                             'screenshot%s.png' % str(self._id))):
            log.error(
                "checkPogoButton: screenshot.png is not a file/corrupted")
            return False

        log.info("checkPogoButton: checking for buttons")
        found = self._pogoWindowManager.lookForButton(
            os.path.join(self._applicationArgs.temp_path,
                         'screenshot%s.png' % str(self._id)), 2.20, 3.01,
            self._communicator)
        if found:
            log.info("checkPogoButton: Found button (small)")
            log.info("checkPogoButton: done")
            return True
        log.info("checkPogoButton: done")
        return False

    def _checkPogoClose(self):
        log.debug("checkPogoClose: Trying to find closeX")
        pogoTopmost = self._communicator.isPogoTopmost()
        if not pogoTopmost:
            return False

        self._checkPogoFreeze()
        if not self._takeScreenshot(
                delayBefore=self._applicationArgs.post_screenshot_delay):
            # TODO: go again?
            # if again:
            #     log.error("checkPogoClose: failed getting a screenshot again")
            #     return False
            # TODO: consider throwing?
            log.debug("checkPogoClose: Could not get screenshot")
            return False
        attempts = 0

        if os.path.isdir(
                os.path.join(self._applicationArgs.temp_path,
                             'screenshot%s.png' % str(self._id))):
            log.error("checkPogoClose: screenshot.png is not a file/corrupted")
            return False

        log.info("checkPogoClose: checking for CloseX")
        found = self._pogoWindowManager.checkCloseExceptNearbyButton(
            os.path.join(self._applicationArgs.temp_path,
                         'screenshot%s.png' % str(self._id)), self._id,
            self._communicator)
        if found:
            log.info("checkPogoClose: Found (X) button (except nearby)")
            log.info("checkPogoClose: done")
            return True
        log.info("checkPogoClose: done")
        return False

    def _getToRaidscreen(self, maxAttempts, again=False):
        # check for any popups (including post login OK)
        log.debug(
            "getToRaidscreen: Trying to get to the raidscreen with %s max attempts..."
            % str(maxAttempts))
        pogoTopmost = self._communicator.isPogoTopmost()
        if not pogoTopmost:
            return False

        self._checkPogoFreeze()
        if not self._takeScreenshot(
                delayBefore=self._applicationArgs.post_screenshot_delay):
            if again:
                log.error("getToRaidscreen: failed getting a screenshot again")
                return False
            self._getToRaidscreen(maxAttempts, True)
            log.debug("getToRaidscreen: Got screenshot, checking GPS")
        attempts = 0

        if os.path.isdir(
                os.path.join(self._applicationArgs.temp_path,
                             'screenshot%s.png' % str(self._id))):
            log.error(
                "getToRaidscreen: screenshot.png is not a file/corrupted")
            return False

        # TODO: replace self._id with device ID
        while self._pogoWindowManager.isGpsSignalLost(
                os.path.join(self._applicationArgs.temp_path,
                             'screenshot%s.png' % str(self._id)), self._id):
            log.debug("getToRaidscreen: GPS signal lost")
            time.sleep(1)
            self._takeScreenshot()
            log.warning("getToRaidscreen: GPS signal error")
            self._redErrorCount += 1
            if self._redErrorCount > 3:
                log.error(
                    "getToRaidscreen: Red error multiple times in a row, restarting"
                )
                self._redErrorCount = 0
                self._restart_pogo()
                return False
        self._redErrorCount = 0
        log.debug("getToRaidscreen: checking raidscreen")
        while not self._pogoWindowManager.checkRaidscreen(
                os.path.join(self._applicationArgs.temp_path,
                             'screenshot%s.png' % str(self._id)), self._id,
                self._communicator):
            log.debug("getToRaidscreen: not on raidscreen...")
            if attempts > maxAttempts:
                # could not reach raidtab in given maxAttempts
                log.error(
                    "getToRaidscreen: Could not get to raidtab within %s attempts"
                    % str(maxAttempts))
                return False
            self._checkPogoFreeze()
            # not using continue since we need to get a screen before the next round...
            found = self._pogoWindowManager.lookForButton(
                os.path.join(self._applicationArgs.temp_path,
                             'screenshot%s.png' % str(self._id)), 2.20, 3.01,
                self._communicator)
            if found:
                log.info("getToRaidscreen: Found button (small)")

            if not found and self._pogoWindowManager.checkCloseExceptNearbyButton(
                    os.path.join(self._applicationArgs.temp_path,
                                 'screenshot%s.png' % str(self._id)), self._id,
                    self._communicator):
                log.info("getToRaidscreen: Found (X) button (except nearby)")
                found = True

            if not found and self._pogoWindowManager.lookForButton(
                    os.path.join(self._applicationArgs.temp_path,
                                 'screenshot%s.png' % str(self._id)), 1.05,
                    2.20, self._communicator):
                log.info("getToRaidscreen: Found button (big)")
                found = True

            log.info("getToRaidscreen: Previous checks found popups: %s" %
                     str(found))
            if not found:
                log.info(
                    "getToRaidscreen: Previous checks found nothing. Checking nearby open"
                )
                if self._pogoWindowManager.checkNearby(
                        os.path.join(self._applicationArgs.temp_path,
                                     'screenshot%s.png' % str(self._id)),
                        self._id, self._communicator):
                    return self._takeScreenshot(
                        delayBefore=self._applicationArgs.post_screenshot_delay
                    )

            if not self._takeScreenshot(
                    delayBefore=self._applicationArgs.post_screenshot_delay):
                return False

            attempts += 1
        log.debug("getToRaidscreen: done")
        return True

    def _get_screen_size(self):
        screen = self._communicator.getscreensize().split(' ')
        self._screen_x = screen[0]
        self._screen_y = screen[1]
        log.debug('Get Screensize of %s: X: %s, Y: %s' %
                  (str(self._id), str(self._screen_x), str(self._screen_y)))
        self._resocalc.get_x_y_ratio(self, self._screen_x, self._screen_y)
Ejemplo n.º 3
0
class WorkerConfigmode(object):
    def __init__(self, args, id, websocket_handler, walker, mapping_manager,
                 mitm_mapper: MitmMapper, db_wrapper: DbWrapperBase):
        self._communicator = Communicator(websocket_handler, id, self,
                                          args.websocket_command_timeout)
        self._stop_worker_event = Event()
        self._id = id
        self._walker = walker
        self.workerstart = None
        self._mapping_manager: MappingManager = mapping_manager
        self._mitm_mapper = mitm_mapper
        self._db_wrapper = db_wrapper

    def set_devicesettings_value(self, key: str, value):
        self._mapping_manager.set_devicesetting_value_of(self._id, key, value)

    def get_devicesettings_value(self, key: str, default_value: object = None):
        devicemappings: Optional[
            dict] = self._mapping_manager.get_devicemappings_of(self._id)
        if devicemappings is None:
            return default_value
        return devicemappings.get("settings", {}).get(key, default_value)

    def get_communicator(self):
        return self._communicator

    def start_worker(self):
        logger.info("Worker {} started in configmode", str(self._id))
        while self.check_walker() and not self._stop_worker_event.is_set():
            time.sleep(10)
        self.set_devicesettings_value('finished', True)
        try:
            self._communicator.cleanup_websocket()
        finally:
            logger.info("Internal cleanup of {} finished", str(self._id))
        return

    def stop_worker(self):
        if self._stop_worker_event.set():
            logger.info('Worker {} already stopped - waiting for it',
                        str(self._id))
        else:
            self._stop_worker_event.set()
            logger.warning("Worker {} stop called", str(self._id))

    def set_geofix_sleeptime(self, sleeptime):
        return True

    def check_walker(self):
        mode = self._walker['walkertype']
        if mode == "countdown":
            logger.info("Checking walker mode 'countdown'")
            countdown = self._walker['walkervalue']
            if not countdown:
                logger.error(
                    "No Value for Mode - check your settings! Killing worker")
                return False
            if self.workerstart is None:
                self.workerstart = math.floor(time.time())
            else:
                if math.floor(
                        time.time()) >= int(self.workerstart) + int(countdown):
                    return False
            return True
        elif mode == "timer":
            logger.debug("Checking walker mode 'timer'")
            exittime = self._walker['walkervalue']
            if not exittime or ':' not in exittime:
                logger.error(
                    "No or wrong Value for Mode - check your settings! Killing worker"
                )
                return False
            return check_walker_value_type(exittime)
        elif mode == "round":
            logger.error("Rounds while sleep - HAHAHAH")
            return False
        elif mode == "period":
            logger.debug("Checking walker mode 'period'")
            period = self._walker['walkervalue']
            if len(period) == 0:
                logger.error(
                    "No Value for Mode - check your settings! Killing worker")
                return False
            return check_walker_value_type(period)
        elif mode == "coords":
            exittime = self._walker['walkervalue']
            if len(exittime) > 0:
                return check_walker_value_type(exittime)
            return True
        elif mode == "idle":
            logger.debug("Checking walker mode 'idle'")
            if len(self._walker['walkervalue']) == 0:
                logger.error(
                    "Wrong Value for mode - check your settings! Killing worker"
                )
                return False
            sleeptime = self._walker['walkervalue']
            logger.info('{} going to sleep', str(self._id))
            killpogo = False
            if check_walker_value_type(sleeptime):
                self._stop_pogo()
                killpogo = True
            while check_walker_value_type(
                    sleeptime) and not self._stop_worker_event.isSet():
                time.sleep(1)
            logger.info('{} just woke up', str(self._id))
            if killpogo:
                try:
                    self._start_pogo()
                except (WebsocketWorkerRemovedException,
                        WebsocketWorkerTimeoutException):
                    logger.error("Timeout during init of worker {}",
                                 str(self._id))
            return False
        else:
            logger.error("Unknown walker mode! Killing worker")
            return False

    def _stop_pogo(self):
        attempts = 0
        stop_result = self._communicator.stopApp("com.nianticlabs.pokemongo")
        pogoTopmost = self._communicator.isPogoTopmost()
        while pogoTopmost:
            attempts += 1
            if attempts > 10:
                return False
            stop_result = self._communicator.stopApp(
                "com.nianticlabs.pokemongo")
            time.sleep(1)
            pogoTopmost = self._communicator.isPogoTopmost()
        return stop_result

    def _start_pogo(self):
        pogo_topmost = self._communicator.isPogoTopmost()
        if pogo_topmost:
            return True

        if not self._communicator.isScreenOn():
            self._communicator.startApp("de.grennith.rgc.remotegpscontroller")
            logger.warning("Turning screen on")
            self._communicator.turnScreenOn()
            time.sleep(
                self.get_devicesettings_value("post_turn_screen_on_delay", 7))

        start_result = False
        while not pogo_topmost:
            self._mitm_mapper.set_injection_status(self._id, False)
            start_result = self._communicator.startApp(
                "com.nianticlabs.pokemongo")
            time.sleep(1)
            pogo_topmost = self._communicator.isPogoTopmost()

        reached_raidtab = False
        self._wait_pogo_start_delay()

        return reached_raidtab

    def _wait_for_injection(self):
        self._not_injected_count = 0
        while not self._mitm_mapper.get_injection_status(self._id):
            if self._not_injected_count >= 20:
                logger.error("Worker {} not get injected in time - reboot",
                             str(self._id))
                self._reboot()
                return False
            logger.info("Worker {} is not injected till now (Count: {})",
                        str(self._id), str(self._not_injected_count))
            if self._stop_worker_event.isSet():
                logger.error(
                    "Worker {} get killed while waiting for injection",
                    str(self._id))
                return False
            self._not_injected_count += 1
            wait_time = 0
            while wait_time < 20:
                wait_time += 1
                if self._stop_worker_event.isSet():
                    logger.error(
                        "Worker {} get killed while waiting for injection",
                        str(self._id))
                    return False
                time.sleep(1)
        return True

    def _reboot(self):
        if not self.get_devicesettings_value("reboot", True):
            logger.warning(
                "Reboot command to be issued to device but reboot is disabled. Skipping reboot"
            )
            return True
        try:
            start_result = self._communicator.reboot()
        except WebsocketWorkerRemovedException:
            logger.error(
                "Could not reboot due to client already having disconnected")
            start_result = False
        time.sleep(5)
        self._db_wrapper.save_last_reboot(self._id)
        self.stop_worker()
        return start_result

    def _wait_pogo_start_delay(self):
        delay_count: int = 0
        pogo_start_delay: int = self.get_devicesettings_value(
            "post_pogo_start_delay", 60)
        logger.info('Waiting for pogo start: {} seconds',
                    str(pogo_start_delay))

        while delay_count <= pogo_start_delay:
            if self._stop_worker_event.is_set():
                logger.error(
                    "Worker {} get killed while waiting for pogo start",
                    str(self._id))
                raise InternalStopWorkerException
            time.sleep(1)
            delay_count += 1