123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228 |
- # 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
- <https://darksky.net/poweredby/>`__. 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')
-
- key = self.options['api_key']
- lat = self.options['latitude']
- lon = self.options['longitude']
- self._url = f'https://api.darksky.net/forecast/{key}/{lat},{lon}'
-
- 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._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 f'{temperature:.{self._temp_precision}f} degrees {unit}'
-
- def _format_percent(self, value):
- """Format a percentage value for the TTS system"""
- return f'{100*value:.0f} percent'
-
- 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 f'{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)
- summary = self.cache['currently']['summary']
- temperature = self._format_temperature(
- self.cache['currently']['temperature'])
- humidity = self._format_percent(self.cache['currently']['humidity'])
- tts(f'{summary}, {temperature}, relative humidity {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/.')
|