# This is part of Kaylee # -- this code is licensed GPLv3 # Copyright 2015-2017 Clayton G. Hobbs # Portions Copyright 2013 Jezra """Dark Sky weather plugin for Kaylee This plugin provides weather information `powered by Dark Sky `__. The user must provide a key for the Dark Sky API, as well as decimal latitude and longitude:: ".darksky": { "api_key": "USER_API_KEY", "latitude": USER_LATITUDE, "longitude": USER_LONGITUDE } Weather information is cached for up to ``cache_max_age`` seconds (default 1800, half an hour). To conserve API calls, the cache is not updated until the first time the user requests weather information after the cache becomes stale. Temperatures are read to the nearest ``temp_precision`` decimal places (default 0). """ import json import os import time import requests from . import PluginBase, Handler class Plugin(PluginBase): """Main class for the Dark Sky weather plugin""" def __init__(self, config, name): """Initialize the Dark Sky weather plugin""" super().__init__(config, name) self._cache_filename = os.path.join(config.cache_dir, 'darksky.json') self._weather_url = 'https://api.darksky.net/forecast/{}/{},{}'.format( self.options['api_key'], self.options['latitude'], self.options['longitude'] ) try: self._cache_max_age = self.options['cache_max_age'] except KeyError: self._cache_max_age = 1800 try: self._temp_precision = self.options['temp_precision'] except KeyError: self._temp_precision = 0 self.commands = { 'whats the temperature': DarkSkyTemperatureHandler, 'whats todays high': DarkSkyTodaysHighHandler, 'whats todays low': DarkSkyTodaysLowHandler, 'whats tomorrows high': DarkSkyTomorrowsHighHandler, 'whats the humidity': DarkSkyHumidityHandler, 'whats the weather': DarkSkyCurrentConditionsHandler, 'whens sunset': DarkSkySunsetHandler, 'hoos your weather provider': DarkSkyProviderHandler } self.corpus_strings.update(self.commands) def get_handler(self, text): """Return a handler if a recognized command is heard""" if text in self.corpus_strings: return self.commands[text](1, self._cache_filename, self._weather_url, self._cache_max_age, self._temp_precision) else: return None class DarkSkyHandler(Handler): """Base class for Dark Sky plugin handlers""" def __init__(self, confidence, cache_filename, url, cache_max_age, temp_precision): super().__init__(confidence) self._cache_filename = cache_filename self._url = url self._cache_max_age = cache_max_age self._temp_precision = temp_precision def _update_cache_if_stale(self): try: st = os.stat(self._cache_filename) except FileNotFoundError: # If there's no cache file, we need to get one self._update_cache() else: if time.time() - st.st_mtime > self._cache_max_age: self._update_cache() def _update_cache(self): r = requests.get(self._url, stream=True) if r.status_code == 200: with open(self._cache_filename, 'wb') as f: for chunk in r: f.write(chunk) def _read_cache(self): with open(self._cache_filename, 'r') as f: return json.load(f) def _format_temperature(self, temperature, unit='Fahrenheit'): """Format a temperature for the TTS system""" return '{:.{prec}f} degrees {}'.format(temperature, unit, prec=self._temp_precision) def _format_percent(self, value): """Format a percentage value for the TTS system""" return '{:.0f} percent'.format(100 * value) def _format_time(self, epoch_time): """Format a time in seconds since the epoch for the TTS system""" t = time.localtime(epoch_time) # Format each part of the time to be pronounced nicely hour = time.strftime('%I', t) if hour[0] == '0': hour = hour[1] minute = time.strftime('%M', t) if minute[0] == '0': if minute[1] == '0': minute = "o'clock" else: minute = 'O' + minute[1] ante_post = time.strftime('%p', t) if ante_post[0] == 'A': ante_post = 'AE ' + ante_post[1] # Put the parts together return '{} {} {}'.format(hour, minute, ante_post) def __call__(self, tts): """Update the cache if necessary and load it""" self._update_cache_if_stale() self.cache = self._read_cache() class DarkSkyTemperatureHandler(DarkSkyHandler): """Handler to speak the current temperature""" def __call__(self, tts): """Speak the current temperature""" super().__call__(tts) tts(self._format_temperature(self.cache['currently']['temperature'])) class DarkSkyTodaysHighHandler(DarkSkyHandler): """Handler to speak today's high temperature""" def __call__(self, tts): """Speak today's high""" super().__call__(tts) tts(self._format_temperature( self.cache['daily']['data'][0]['temperatureMax'])) class DarkSkyTodaysLowHandler(DarkSkyHandler): """Handler to speak today's low temperature""" def __call__(self, tts): """Speak today's low""" super().__call__(tts) tts(self._format_temperature( self.cache['daily']['data'][0]['temperatureMin'])) class DarkSkyTomorrowsHighHandler(DarkSkyHandler): """Handler to speak tomorrow's high temperature""" def __call__(self, tts): """Speak tomorrow's high""" super().__call__(tts) tts(self._format_temperature( self.cache['daily']['data'][1]['temperatureMax'])) class DarkSkyHumidityHandler(DarkSkyHandler): """Handler to speak the current relative humidity""" def __call__(self, tts): """Speak the current relative humidity""" super().__call__(tts) tts(self._format_percent(self.cache['currently']['humidity'])) class DarkSkyCurrentConditionsHandler(DarkSkyHandler): """Handler to speak the current weather conditions""" def __call__(self, tts): """Speak the current weather conditions""" super().__call__(tts) tts('{}, {}, relative humidity {}'.format( self.cache['currently']['summary'], self._format_temperature(self.cache['currently']['temperature']), self._format_percent(self.cache['currently']['humidity']))) class DarkSkySunsetHandler(DarkSkyHandler): """Handler to speak the time of sunset""" def __call__(self, tts): """Speak the time of sunset""" super().__call__(tts) tts(self._format_time(self.cache['daily']['data'][0]['sunsetTime'])) class DarkSkyProviderHandler(DarkSkyHandler): """Handler to speak information about the weather provider""" def __call__(self, tts): """Speak information about the weather provider""" # No need for super since this doesn't need any weather information tts('Powered by Dark Sky, https:// dark sky .net/ powered by/.')