예제 #1
0
def check_existing() -> bool:
    """Checks for existing connection.

    Returns:
        bool:
        A boolean flag whether a valid connection is present.
    """
    if is_port_in_use(port=env.speech_synthesis_port):
        logger.info(f'{env.speech_synthesis_port} is currently in use.')
        try:
            res = requests.get(
                url=
                f"http://{env.speech_synthesis_host}:{env.speech_synthesis_port}",
                timeout=1)
            if res.ok:
                logger.info(
                    f'http://{env.speech_synthesis_host}:{env.speech_synthesis_port} is accessible.'
                )
                return True
            return False
        except (ConnectionError, TimeoutError, requests.exceptions.RequestException, requests.exceptions.Timeout) as \
                error:
            logger.error(error)
            if not kill_port_pid(port=env.speech_synthesis_port):
                logger.critical(
                    'Failed to kill existing PID. Attempting to re-create session.'
                )
예제 #2
0
def trigger_api() -> None:
    """Initiates the fast API in a dedicated process using uvicorn server.

    See Also:
        - Checks if the port is being used. If so, makes a ``GET`` request to the endpoint.
        - Attempts to kill the process listening to the port, if the endpoint doesn't respond.
    """
    url = f'http://{env.offline_host}:{env.offline_port}'

    if is_port_in_use(port=env.offline_port):
        logger.info(f'{env.offline_port} is currently in use.')

        try:
            res = requests.get(url=url, timeout=1)
            if res.ok:
                logger.info(f'{url} is accessible.')
                return
            raise requests.exceptions.ConnectionError
        except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
            logger.error('Unable to connect to existing uvicorn server.')

        if not kill_port_pid(port=env.offline_port):  # This might terminate Jarvis
            logger.critical('Failed to kill existing PID. Attempting to re-create session.')

    argument_dict = {
        "app": "api.fast:app",
        "host": env.offline_host,
        "port": env.offline_port,
        "reload": True
    }
    if not env.macos:
        del argument_dict['reload']

    config = uvicorn.Config(**argument_dict)
    APIServer(config=config).run_in_parallel()
예제 #3
0
def unrecognized_dumper(train_data: dict) -> NoReturn:
    """If none of the conditions are met, converted text is written to a yaml file for training purpose.

    Args:
        train_data: Takes the dictionary that has to be written as an argument.
    """
    dt_string = datetime.now().strftime("%B %d, %Y %H:%M:%S.%f")[:-3]
    data = {}
    if os.path.isfile(fileio.training):
        try:
            with open(fileio.training) as reader:
                data = yaml.load(stream=reader, Loader=yaml.FullLoader) or {}
        except yaml.YAMLError as error:
            logger.error(error)
            os.rename(src=fileio.training,
                      dst=str(fileio.training).replace(".", f"_{datetime.now().strftime('%m_%d_%Y_%H_%M')}."))
        for key, value in train_data.items():
            if data.get(key):
                data[key].update({dt_string: value})
            else:
                data.update({key: {dt_string: value}})

    if not data:
        data = {key1: {dt_string: value1} for key1, value1 in train_data.items()}

    data = {
        func: {
            dt: unrec for dt, unrec in sorted(unrec_dict.items(), reverse=True,
                                              key=lambda item: datetime.strptime(item[0], "%B %d, %Y %H:%M:%S.%f"))
        } for func, unrec_dict in data.items()
    }

    with open(fileio.training, 'w') as writer:
        yaml.dump(data=data, stream=writer, sort_keys=False)
예제 #4
0
    def stop(self) -> NoReturn:
        """Invoked when the run loop is exited or manual interrupt.

        See Also:
            - Terminates/Kills all the background processes.
            - Releases resources held by porcupine.
            - Closes audio stream.
            - Releases port audio resources.
        """
        stop_processes()
        delete_db()
        logger.info("Releasing resources acquired by Porcupine.")
        self.detector.delete()
        if self.audio_stream and self.audio_stream.is_active():
            logger.info("Closing Audio Stream.")
            self.py_audio.close(stream=self.audio_stream)
            self.audio_stream.close()
        logger.info("Releasing PortAudio resources.")
        self.py_audio.terminate()
        if not env.save_audio_timeout:
            return
        sys.stdout.write("\rRecording is being converted to audio.")
        logger.info("Recording is being converted to audio.")
        response = timeout.timeout(function=save_audio, seconds=env.save_audio_timeout, logger=logger,
                                   kwargs={"frames": self.recorded_frames, "sample_rate": self.detector.sample_rate})
        if response.ok:
            logger.info("Recording has been saved successfully.")
            logger.info(response.info)
        else:
            logger.error(f"Failed to save the audio file within {env.save_audio_timeout} seconds.")
            logger.error(response.info)
예제 #5
0
        def wrapper(*args: Any, **kwargs: Any) -> Callable:
            """Executes the wrapped function in a loop for the number of attempts mentioned.

            Args:
                *args: Arguments.
                **kwargs: Keyword arguments.

            Returns:
                Callable:
                Return value of the function implemented.

            Raises:
                Raises the exception as received beyond the given number of attempts.
            """
            return_exc = None
            for i in range(1, attempts + 1):
                try:
                    return_val = func(*args, **kwargs)
                    # Log messages only when the function did not return during the first attempt
                    logger.info(msg=f"{func.__name__} returned at {engine().ordinal(num=i)} attempt") if i > 1 else None
                    return return_val
                except exclude_exc or KeyboardInterrupt as excl_error:
                    logger.error(msg=excl_error)
                except Exception as error:
                    return_exc = error
                time.sleep(interval)
            logger.error(msg=f"{func.__name__} exceeded retry count::{attempts}")
            if return_exc and warn:
                warnings.warn(
                    f"{type(return_exc).__name__}: {return_exc}"
                )
            elif return_exc:
                raise return_exc
예제 #6
0
def meaning(term: str) -> Union[Dict, None]:
    """Gets the meaning of a word from `wordnetweb.princeton.edu <http://wordnetweb.princeton.edu/perl/webwn?s=>`__.

    Args:
        term: Word for which the meaning has to be fetched.

    Returns:
        dict:
        A dictionary of the part of speech and the meaning of the word.
    """
    response = requests.get(
        f"http://wordnetweb.princeton.edu/perl/webwn?s={term}")
    if not response.ok:
        logger.error('Failed to get the meaning')
        return
    html = BeautifulSoup(response.text, "html.parser")
    types = html.findAll("h3")
    lists = html.findAll("ul")
    out = {}
    for a in types:
        reg = str(lists[types.index(a)])
        meanings = []
        for x in re.findall(r'\((.*?)\)', reg):
            if 'often followed by' in x:
                pass
            elif len(x) > 5 or ' ' in str(x):
                meanings.append(x)
        name = a.text
        out[name] = meanings
    return out
예제 #7
0
    def __init__(self, device_ip: str, device_type: int, operation: str):
        """Initialize a device.

        Args:
            device_ip: Takes device IP address as argument.
            device_type: Specific device type. Commonly 1 or 2.
            operation: Takes the operation that the calling function is trying to perform and logs it.
        """
        self.device_ip = device_ip
        self.device_type = device_type
        self.operation = operation
        self.API_PORT = 5577
        self.latest_connection = time.time()
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.settimeout(3)
        try:
            # Establishing connection with the device.
            self.sock.connect((self.device_ip, self.API_PORT))
        except socket.error as error:
            self.sock.close()
            error_msg = f"\rSocket error on {device_ip}: {error}"
            logger.error(f'{error_msg} while performing "{self.operation}"')
            raise socket.error(
                error
            )
예제 #8
0
def frequently_used(function_name: str) -> NoReturn:
    """Writes the function called and the number of times into a yaml file.

    Args:
        function_name: Name of the function that called the speaker.

    See Also:
        - This function does not have purpose, but to analyze and re-order the conditions' module at a later time.
    """
    if os.path.isfile(fileio.frequent):
        try:
            with open(fileio.frequent) as file:
                data = yaml.load(stream=file, Loader=yaml.FullLoader) or {}
        except yaml.YAMLError as error:
            data = {}
            logger.error(error)
        if data.get(function_name):
            data[function_name] += 1
        else:
            data[function_name] = 1
    else:
        data = {function_name: 1}
    with open(fileio.frequent, 'w') as file:
        yaml.dump(data={
            k: v
            for k, v in sorted(data.items(), key=lambda x: x[1], reverse=True)
        },
                  stream=file,
                  sort_keys=False)
예제 #9
0
def threat_notify(converted: str, date_extn: Union[str, None], gmail_user: str, gmail_pass: str,
                  phone_number: str, recipient: str) -> NoReturn:
    """Sends an SMS and email notification in case of a threat.

    References:
        Uses `gmail-connector <https://pypi.org/project/gmail-connector/>`__ to send the SMS and email.

    Args:
        converted: Takes the voice recognized statement as argument.
        date_extn: Name of the attachment file which is the picture of the intruder.
        gmail_user: Email address for the gmail account.
        gmail_pass: Password of the gmail account.
        phone_number: Phone number to send SMS.
        recipient: Email address of the recipient.
    """
    if converted:
        communicator.notify(user=gmail_user, password=gmail_pass, number=phone_number, subject="!!INTRUDER ALERT!!",
                            body=f"{datetime.now().strftime('%B %d, %Y %I:%M %p')}\n{converted}")
        body_ = f"""<html><head></head><body><h2>Conversation of Intruder:</h2><br>{converted}<br><br>
                                    <h2>Attached is a photo of the intruder.</h2>"""
    else:
        communicator.notify(user=gmail_user, password=gmail_pass, number=phone_number, subject="!!INTRUDER ALERT!!",
                            body=f"{datetime.now().strftime('%B %d, %Y %I:%M %p')}\n"
                                 "Check your email for more information.")
        body_ = """<html><head></head><body><h2>No conversation was recorded,
                                but attached is a photo of the intruder.</h2>"""
    if date_extn:
        attachment_ = f'threat/{date_extn}.jpg'
        response_ = SendEmail(gmail_user=gmail_user, gmail_pass=gmail_pass,
                              recipient=recipient, body=body_, attachment=attachment_,
                              subject=f"Intruder Alert on {datetime.now().strftime('%B %d, %Y %I:%M %p')}").send_email()
        if response_.ok:
            logger.info('Email has been sent!')
        else:
            logger.error(f"Email dispatch failed with response: {response_.body}\n")
예제 #10
0
def listen(timeout: Union[int, float], phrase_limit: Union[int, float], sound: bool = True,
           stdout: bool = True) -> Union[str, None]:
    """Function to activate listener, this function will be called by most upcoming functions to listen to user input.

    Args:
        timeout: Time in seconds for the overall listener to be active.
        phrase_limit: Time in seconds for the listener to actively listen to a sound.
        sound: Flag whether to play the listener indicator sound. Defaults to True unless set to False.
        stdout: Flag whether to print the listener status on screen.

    Returns:
        str:
         - Returns recognized statement from the microphone.
    """
    with Microphone() as source:
        try:
            playsound(sound=indicators.start, block=False) if sound else None
            sys.stdout.write("\rListener activated...") if stdout else None
            listened = recognizer.listen(source=source, timeout=timeout, phrase_time_limit=phrase_limit)
            playsound(sound=indicators.end, block=False) if sound else None
            support.flush_screen()
            recognized = recognizer.recognize_google(audio_data=listened)
            logger.info(recognized)
            return recognized
        except (UnknownValueError, RequestError, WaitTimeoutError):
            return
        except (ConnectionError, TimeoutError, requests.exceptions.RequestException, requests.exceptions.Timeout) as \
                error:
            logger.error(error)
예제 #11
0
def get_ssid() -> Union[str, None]:
    """Gets SSID of the network connected.

    Returns:
        str:
        Wi-Fi or Ethernet SSID.
    """
    try:
        process = subprocess.Popen([
            "/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport",
            "-I"
        ],
                                   stdout=subprocess.PIPE
                                   ) if env.macos else subprocess.check_output(
                                       "netsh wlan show interfaces")
    except (subprocess.CalledProcessError, subprocess.CalledProcessError,
            FileNotFoundError) as error:
        logger.error(error)
        return
    if env.macos:
        out, err = process.communicate()
        if error := process.returncode:
            logger.error(
                f"Failed to fetch SSID with exit code: {error}\n{err}")
            return
        # noinspection PyTypeChecker
        return dict(
            map(str.strip, info.split(": "))
            for info in out.decode("utf-8").splitlines()[:-1]
            if len(info.split()) == 2).get("SSID")
예제 #12
0
def ip_info(phrase: str) -> None:
    """Gets IP address of the host machine.

    Args:
        phrase: Takes the spoken phrase an argument and tells the public IP if requested.
    """
    if "public" in phrase.lower():
        if not internet_checker():
            speaker.speak(
                text=f"You are not connected to the internet {env.title}!")
            return
        if ssid := get_ssid():
            ssid = f"for the connection {ssid} "
        else:
            ssid = ""
        output = None
        try:
            output = f"My public IP {ssid}is " \
                     f"{json.load(urllib.request.urlopen(url='https://ipinfo.io/json')).get('ip')}"
        except (urllib.error.HTTPError, urllib.error.URLError) as error:
            logger.error(error)
        try:
            output = output or f"My public IP {ssid}is " \
                               f"{json.loads(urllib.request.urlopen(url='http://ip.jsontest.com').read()).get('ip')}"
        except (urllib.error.HTTPError, urllib.error.URLError) as error:
            logger.error(error)
        if not output:
            output = f"I was unable to fetch the public IP {env.title}!"
예제 #13
0
파일: tv.py 프로젝트: thevickypedia/Jarvis
    def tv_status(attempt: int = 0) -> str:
        """Pings the tv and returns the status. 0 if able to ping, 256 if unable to ping.

        Args:
            attempt: Takes iteration count as an argument.

        Returns:
            int:
            Returns the reachable IP address from the list.
        """
        for ip in tv_ip_list:
            if env.macos:
                if tv_stat := os.system(
                        f"ping -c 1 -t 2 {ip} >/dev/null 2>&1"):
                    logger.error(
                        f"Connection timed out on {ip}. Ping result: {tv_stat}"
                    ) if not attempt else None
                else:
                    return ip
            else:
                if tv_stat := os.system(f"ping -c 1 -t 2 {ip} > NUL"):
                    logger.error(
                        f"Connection timed out on {ip}. Ping result: {tv_stat}"
                    ) if not attempt else None
                else:
예제 #14
0
def automator() -> NoReturn:
    """Place for long-running background tasks.

    See Also:
        - The automation file should be a dictionary within a dictionary that looks like the below:

            .. code-block:: yaml

                6:00 AM:
                  task: set my bedroom lights to 50%
                9:00 PM:
                  task: set my bedroom lights to 5%

        - Jarvis creates/swaps a ``status`` flag upon execution, so that it doesn't repeat execution within a minute.
    """
    offline_list = compatibles.offline_compatible() + keywords.restart_control
    start_events = start_meetings = time.time()
    events.event_app_launcher()
    dry_run = True
    while True:
        if os.path.isfile(fileio.automation):
            if exec_task := auto_helper(offline_list=offline_list):
                offline_communicator(command=exec_task)

        if start_events + env.sync_events <= time.time() or dry_run:
            start_events = time.time()
            event_process = Process(target=events.events_writer)
            logger.info(f"Getting calendar events from {env.event_app}") if dry_run else None
            event_process.start()
            with db.connection:
                cursor = db.connection.cursor()
                cursor.execute("INSERT INTO children (events) VALUES (?);", (event_process.pid,))
                db.connection.commit()

        if start_meetings + env.sync_meetings <= time.time() or dry_run:
            if dry_run and env.ics_url:
                try:
                    if requests.get(url=env.ics_url).status_code == 503:
                        env.sync_meetings = 21_600  # Set to 6 hours if unable to connect to the meetings URL
                except (ConnectionError, TimeoutError, requests.exceptions.RequestException,
                        requests.exceptions.Timeout) as error:
                    logger.error(error)
                    env.sync_meetings = 99_999_999  # NEVER RUN, since env vars are loaded only once during start up
            start_meetings = time.time()
            meeting_process = Process(target=icalendar.meetings_writer)
            logger.info("Getting calendar schedule from ICS.") if dry_run else None
            meeting_process.start()
            with db.connection:
                cursor = db.connection.cursor()
                cursor.execute("INSERT INTO children (meetings) VALUES (?);", (meeting_process.pid,))
                db.connection.commit()

        if alarm_state := support.lock_files(alarm_files=True):
            for each_alarm in alarm_state:
                if each_alarm == datetime.now().strftime("%I_%M_%p.lock"):
                    Process(target=alarm_executor).start()
                    os.remove(os.path.join("alarm", each_alarm))
예제 #15
0
def internet_checker() -> Union[Speedtest, bool]:
    """Uses speed test api to check for internet connection.

    Returns:
        ``Speedtest`` or bool:
        - On success, returns Speedtest module.
        - On failure, returns boolean False.
    """
    try:
        return Speedtest()
    except ConfigRetrievalError as error:
        logger.error(error)
        return False
예제 #16
0
def location() -> NoReturn:
    """Gets the user's current location."""
    try:
        with open(fileio.location) as file:
            current_location = yaml.load(stream=file, Loader=yaml.FullLoader)
    except yaml.YAMLError as error:
        logger.error(error)
        speaker.speak(text=f"I'm sorry {env.title}! I wasn't able to get the location details. Please check the logs.")
        return
    speaker.speak(text=f"I'm at {current_location.get('address', {}).get('road', '')} - "
                       f"{current_location.get('address', {}).get('city', '')} "
                       f"{current_location.get('address', {}).get('state', '')} - "
                       f"in {current_location.get('address', {}).get('country', '')}")
예제 #17
0
def vehicle(operation: str, temp: int = None) -> Union[str, dict, None]:
    """Establishes a connection with the car and returns an object to control the primary vehicle.

    Args:
        operation: Operation to be performed.
        temp: Temperature for climate control.

    Returns:
        str:
        Returns the vehicle's name.
    """
    try:
        connection = connector.Connect(username=env.car_email,
                                       password=env.car_pass)
        connection.connect()
        if not connection.head:
            return
        vehicles = connection.get_vehicles(
            headers=connection.head).get("vehicles")
        primary_vehicle = [
            each_vehicle for each_vehicle in vehicles
            if each_vehicle.get("role") == "Primary"
        ][0]
        control = controller.Control(vin=primary_vehicle.get("vin"),
                                     connection=connection)

        response = {}
        if operation == "LOCK":
            response = control.lock(pin=env.car_pin)
        elif operation == "UNLOCK":
            response = control.unlock(pin=env.car_pin)
        elif operation == "START":
            lock_status = {
                each_dict['key']: each_dict['value']
                for each_dict in [
                    key for key in control.get_status().get(
                        'vehicleStatus').get('coreStatus') if key.get('key') in
                    ["DOOR_IS_ALL_DOORS_LOCKED", "DOOR_BOOT_LOCK_STATUS"]
                ]
            }
            if lock_status.get('DOOR_IS_ALL_DOORS_LOCKED', 'FALSE') != 'TRUE' or \
                    lock_status.get('DOOR_BOOT_LOCK_STATUS', 'UNLOCKED') != 'LOCKED':
                logger.warning("Car is unlocked when tried to remote start!")
                if lock_response := control.lock(
                        pin=env.car_pin).get("failureDescription"):
                    logger.error(lock_response)
            response = control.remote_engine_start(pin=env.car_pin,
                                                   target_temperature=temp)
        elif operation == "STOP":
            response = control.remote_engine_stop(pin=env.car_pin)
예제 #18
0
def get_location_from_coordinates(coordinates: tuple) -> dict:
    """Uses the latitude and longitude information to get the address information.

    Args:
        coordinates: Takes the latitude and longitude as a tuple.

    Returns:
        dict:
        Location address.
    """
    try:
        locator = geo_locator.reverse(coordinates, language="en")
        return locator.raw["address"]
    except (GeocoderUnavailable, GeopyError) as error:
        logger.error(error)
        return {}
예제 #19
0
 def synthesizer(self) -> NoReturn:
     """Initiates speech synthesizer using docker."""
     if check_existing():
         return
     if not os.path.isfile(fileio.speech_synthesis_log):
         pathlib.Path(fileio.speech_synthesis_log).touch()
     with open(fileio.speech_synthesis_log, "a") as output:
         try:
             subprocess.call(self.docker,
                             shell=True,
                             stdout=output,
                             stderr=output)
         except (subprocess.CalledProcessError, subprocess.SubprocessError,
                 Exception) as error:
             logger.error(error)
             return
예제 #20
0
def kill_port_pid(port: int, protocol: str = 'tcp') -> Union[bool, None]:
    """Uses List all open files ``lsof`` to get the PID of the process that is listening on the given port and kills it.

    Args:
        port: Port number which the application is listening on.
        protocol: Protocol serving the port. Defaults to ``TCP``

    Warnings:
        **Use only when the application listening to given port runs as a dedicated/child process with a different PID**

            - This function will kill the process that is using the given port.
            - If the PID is the same as that of ``MainProcess``, triggers a warning without terminating the process.

    Returns:
        bool:
        Flag to indicate whether the process was terminated successfully.
    """
    try:
        active_sessions = subprocess.check_output(
            f"lsof -i {protocol}:{port}",
            shell=True).decode('utf-8').splitlines()
        for each in active_sessions:
            each_split = each.split()
            if each_split[0].strip() == 'Python':
                logger.info(
                    f'Application hosted on {each_split[-2]} is listening to port: {port}'
                )
                pid = int(each_split[1])
                if pid == os.getpid():
                    called_function = sys._getframe(1).f_code.co_name  # noqa
                    called_file = sys._getframe(1).f_code.co_filename.replace(
                        f'{os.getcwd()}/', '')  # noqa
                    logger.warning(
                        f"{called_function} from {called_file} tried to kill the running process."
                    )
                    warnings.warn(
                        f'OPERATION DENIED: {called_function} from {called_file} tried to kill the running process.'
                    )
                    return
                os.kill(pid, signal.SIGTERM)
                logger.info(f'Killed PID: {pid}')
                return True
        logger.info(f'No active process running on {port}')
        return False
    except (subprocess.SubprocessError,
            subprocess.CalledProcessError) as error:
        logger.error(error)
예제 #21
0
def meetings_gatherer() -> str:
    """Gets ICS data and converts into a statement.

    Returns:
        str:
        - On success, returns a message saying which event is scheduled at what time.
        - If no events, returns a message saying there are no events today.
        - On failure, returns a message saying Jarvis was unable to read the calendar schedule.
    """
    if not env.ics_url:
        return f"I wasn't given a calendar URL to look up your meetings {env.title}!"
    try:
        response = requests.get(url=env.ics_url)
    except (ConnectionError, TimeoutError,
            requests.exceptions.RequestException,
            requests.exceptions.Timeout) as error:
        logger.error(error)
        return f"I was unable to connect to the internet {env.title}! Please check your connection."
    if not response.ok:
        logger.error(response.status_code)
        return "I wasn't able to read your calendar schedule sir! Please check the shared URL."
    calendar = Calendar(response.text)
    events = list(calendar.timeline.today())
    if not events:
        return f"You don't have any meetings today {env.title}!"
    meeting_status, count = "", 0
    for index, event in enumerate(events):
        if event.end.timestamp < int(time.time(
        )):  # Skips if meeting ended earlier than current time
            continue
        count += 1
        begin_local = event.begin.strftime("%I:%M %p")
        if len(events) == 1:
            meeting_status += f"You have an all day meeting {env.title}! {event.name}. " if event.all_day else \
                f"You have a meeting at {begin_local} {env.title}! {event.name}. "
        else:
            meeting_status += f"{event.name} - all day" if event.all_day else f"{event.name} at {begin_local}"
            meeting_status += ', ' if index + 1 < len(events) else '.'
    if count:
        plural = "meeting" if count == 1 else "meetings"
        meeting_status = f"You have {count} {plural} today {env.title}! {meeting_status}"
    else:
        plural = "meeting" if len(events) == 1 else "meetings"
        meeting_status = f"You have no more meetings for rest of the day {env.title}! " \
                         f"However, you had {len(events)} {plural} earlier today. {meeting_status}"
    return meeting_status
예제 #22
0
def audio_to_text(filename: Union[FilePath, str]) -> str:
    """Converts audio to text using speech recognition.

    Args:
        filename: Filename to process the information from.

    Returns:
        str:
        Returns the string converted from the audio file.
    """
    try:
        file = AudioFile(filename_or_fileobject=filename)
        with file as source:
            audio = recognizer.record(source)
        os.remove(filename)
        return recognizer.recognize_google(audio)
    except UnknownValueError:
        logger.error("Unrecognized audio or language.")
예제 #23
0
def get_coordinates_from_ip() -> Tuple[float, float]:
    """Uses public IP to retrieve latitude and longitude. If fails, uses ``Speedtest`` module.

    Returns:
        tuple:
        Returns latitude and longitude as a tuple.
    """
    try:
        info = json.load(urllib.request.urlopen(url="https://ipinfo.io/json"))
        coordinates = tuple(map(float, info.get('loc', '0,0').split(',')))
    except urllib.error.HTTPError as error:
        logger.error(error)
        coordinates = (0.0, 0.0)
    if coordinates == (0.0, 0.0):
        st = Speedtest()
        return float(st.results.client["lat"]), float(st.results.client["lon"])
    else:
        return coordinates
예제 #24
0
    def send_bytes(self, *bytes_) -> NoReturn:
        """Send commands to the device.

        If the device hasn't been communicated to in 5 minutes, reestablish the connection.

        Args:
            *bytes_: Takes a tuple value as argument.
        """
        check_connection_time = time.time() - self.latest_connection
        try:
            if check_connection_time >= 290:
                sys.stdout.write("\rConnection timed out, reestablishing.")
                self.sock.connect((self.device_ip, self.API_PORT))
            message_length = len(bytes_)
            self.sock.send(struct.pack("B" * message_length, *bytes_))
        except socket.error as error:
            error_msg = f"\rSocket error on {self.device_ip}: {error}"
            sys.stdout.write(error_msg)
            logger.error(f'{error_msg} while performing "{self.operation}"')
        self.sock.close()
예제 #25
0
def events_gatherer() -> str:
    """Uses ``applescript`` to fetch events from local Calendar (including subscriptions) or Microsoft Outlook.

    See Also:
        When reading from apple ``Calendar``, the calendar should be named as ``Jarvis``

    Returns:
        str:
        - On success, returns a message saying which event is scheduled at what time.
        - If no events, returns a message saying there are no events in the next 12 hours.
        - On failure, returns a message saying Jarvis was unable to read calendar/outlook.
    """
    if not env.macos:
        return f"Reading events from {env.event_app} is not possible on Windows operating system."
    failure = None
    process = subprocess.Popen(["/usr/bin/osascript", fileio.event_script] +
                               [str(arg) for arg in [1, 3]],
                               stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE)
    out, err = process.communicate()
    os.system(f"git checkout -- {fileio.event_script}"
              )  # Undo the unspecified changes done by ScriptEditor
    if error := process.returncode:  # stores non zero error
        err_msg = err.decode("UTF-8")
        err_code = err_msg.split()[-1].strip()
        if err_code == "(-1728)":  # If 'Jarvis' is unavailable in calendar/outlook application
            logger.warning(f"'Jarvis' is unavailable in {env.event_app}.")
            return f"Jarvis is unavailable in your {env.event_app} {env.title}!"
        elif err_code == "(-1712)":  # If an event takes 2+ minutes, the Apple Event Manager reports a time-out error.
            failure = f"{env.event_app}/event took an unusually long time to respond/complete.\nInclude, " \
                      f"'with timeout of 300 seconds' to your {fileio.event_script} right after the " \
                      f"'tell application {env.event_app}' step and 'end timeout' before the 'end tell' step."
        elif err_code in ["(-10810)", "(-609)", "(-600)"
                          ]:  # If unable to launch the app or app terminates.
            event_app_launcher()
        if not failure:
            failure = f"Unable to read {env.event_app} - [{error}]\n{err_msg}"
        logger.error(failure)
        # failure = failure.replace('"', '')  # An identifier can’t go after this “"”
        # os.system(f"""osascript -e 'display notification "{failure}" with title "Jarvis"'""")
        return f"I was unable to read your {env.event_app} {env.title}! Please make sure it is in sync."
예제 #26
0
    def thread_worker(function_to_call: Callable) -> int:
        """Initiates ``ThreadPoolExecutor`` with in a dedicated thread.

        Args:
            function_to_call: Takes the function/method that has to be called as an argument.
        """
        futures = {}
        executor = ThreadPoolExecutor(max_workers=len(host_ip))
        with executor:
            for iterator in host_ip:
                future = executor.submit(function_to_call, iterator)
                futures[future] = iterator

        thread_except = 0
        for future in as_completed(futures):
            if future.exception():
                thread_except += 1
                logger.error(
                    f'Thread processing for {iterator} received an exception: {future.exception()}'
                )
        return thread_except
예제 #27
0
def stop_child_processes() -> NoReturn:
    """Stops sub processes (for meetings and events) triggered by child processes."""
    with db.connection:
        cursor_ = db.connection.cursor()
        children = cursor_.execute(
            "SELECT meetings, events FROM children").fetchone()
    for pid in children:
        if not pid:
            continue
        try:
            proc = psutil.Process(pid)
        except psutil.NoSuchProcess as error:
            logger.error(
                error
            )  # Occurs commonly since child processes run only for a short time
            continue
        if proc.is_running():
            logger.info(f"Sending [SIGTERM] to child process with PID: {pid}")
            proc.terminate()
        if proc.is_running():
            logger.info(f"Sending [SIGKILL] to child process with PID: {pid}")
            proc.kill()
예제 #28
0
def get_current_temp(location: dict) -> Tuple[Union[int, str], int]:
    """Get the current temperature at a given location.

    Args:
        location: Takes the location information as a dictionary.

    Returns:
        tuple:
        A tuple of current temperature and target temperature.
    """
    try:
        current_temp = int(
            temperature.k2f(arg=json.loads(
                urllib.request.urlopen(
                    url=
                    f"https://api.openweathermap.org/data/2.5/onecall?lat={location['latitude']}&"
                    f"lon={location['longitude']}&exclude=minutely,hourly&appid={env.weather_api}"
                ).read())['current']['temp']))
    except (urllib.error.HTTPError, urllib.error.URLError) as error:
        logger.error(error)
        return "unknown", 66
    target_temp = 83 if current_temp < 45 else 57 if current_temp > 70 else 66
    return f"{current_temp}\N{DEGREE SIGN}F", target_temp
예제 #29
0
def meetings() -> None:
    """Controller for meetings."""
    with db.connection:
        cursor = db.connection.cursor()
        meeting_status = cursor.execute(
            "SELECT info, date FROM ics").fetchone()
    if meeting_status and meeting_status[1] == datetime.now().strftime(
            '%Y_%m_%d'):
        speaker.speak(text=meeting_status[0])
    elif meeting_status:
        Process(target=meetings_writer).start()
        speaker.speak(
            text=
            f"Meetings table is outdated {env.title}. Please try again in a minute or two."
        )
    else:
        if shared.called_by_offline:
            Process(target=meetings_writer).start()
            speaker.speak(
                text=
                f"Meetings table is empty {env.title}. Please try again in a minute or two."
            )
            return
        meeting = ThreadPool(processes=1).apply_async(
            func=meetings_gatherer)  # Runs parallely and awaits completion
        speaker.speak(
            text=f"Please give me a moment {env.title}! I'm working on it.",
            run=True)
        try:
            speaker.speak(text=meeting.get(timeout=60), run=True)
        except ThreadTimeoutError:
            logger.error(
                "Unable to read the calendar schedule within 60 seconds.")
            speaker.speak(
                text=
                f"I wasn't able to read your calendar within the set time limit {env.title}!",
                run=True)
예제 #30
0
def hostname_to_ip(hostname: str) -> list:
    """Uses ``socket.gethostbyname_ex`` to translate a host name to IPv4 address format, extended interface.

    See Also:
        - A host may have multiple interfaces.
        - | In case of true DNS being used or the host entry file is carefully handwritten, the system will look
          | there to find the translation.
        - | But depending on the configuration, the host name can be bound to all the available interfaces, including
          | the loopback ones.
        - ``gethostbyname`` returns the address of the first of those interfaces in its own order.
        - | To get the assigned IP, ``gethostbyname_ex`` is used, which returns a list of all the interfaces, including
          | the host spot connected, and loopback IPs.

    References:
        https://docs.python.org/3/library/socket.html#socket.gethostbyname_ex

    Args:
        hostname: Takes the hostname of a device as an argument.
    """
    try:
        _hostname, _alias_list, _ipaddr_list = socket.gethostbyname_ex(hostname)
    except socket.error as error:
        logger.error(f"{error} on {hostname}")
        return []
    logger.info({"Hostname": _hostname, "Alias": _alias_list, "Interfaces": _ipaddr_list})
    if not _ipaddr_list:
        logger.critical(f"No interfaces found for {hostname}")
    elif len(_ipaddr_list) > 1:
        logger.warning(f"Host {hostname} has multiple interfaces. {_ipaddr_list}")
        return _ipaddr_list
    else:
        ip_addr = ip_address()
        if _ipaddr_list[0].split('.')[0] == ip_addr.split('.')[0]:
            return _ipaddr_list
        logger.error(f"NetworkID of the InterfaceIP of host {hostname} does not match the network id of DeviceIP.")
    return []