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.' )
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()
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)
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)
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
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
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 )
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)
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")
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)
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")
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}!"
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:
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))
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
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', '')}")
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)
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 {}
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
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)
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
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.")
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
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()
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."
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
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()
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
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)
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 []