Somewhat fancy voice command recognition software
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

darksky.py 5.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. # This is part of Kaylee
  2. # -- this code is licensed GPLv3
  3. # Copyright 2015-2017 Clayton G. Hobbs
  4. # Portions Copyright 2013 Jezra
  5. """Dark Sky weather plugin for Kaylee
  6. This plugin provides weather information `powered by Dark Sky
  7. <https://darksky.net/poweredby/>`__. The user must provide a key for
  8. the Dark Sky API, as well as decimal latitude and longitude::
  9. ".darksky": {
  10. "api_key": "USER_API_KEY",
  11. "latitude": USER_LATITUDE,
  12. "longitude": USER_LONGITUDE
  13. }
  14. Weather information is cached for up to ``cache_max_age`` seconds
  15. (default 1800, half an hour). To conserve API calls, the cache is not
  16. updated until the first time the user requests weather information
  17. after the cache becomes stale.
  18. Temperatures are read to the nearest ``temp_precision`` decimal places
  19. (default 0).
  20. """
  21. import json
  22. import os
  23. import time
  24. import requests
  25. from .pluginbase import PluginBase
  26. class Plugin(PluginBase):
  27. """Main class for the Dark Sky weather plugin"""
  28. def __init__(self, config, name):
  29. """Initialize the Dark Sky weather plugin"""
  30. super().__init__(config, name)
  31. self._cache_filename = os.path.join(config.cache_dir, 'darksky.json')
  32. self._weather_url = 'https://api.darksky.net/forecast/{}/{},{}'.format(
  33. self.options['api_key'],
  34. self.options['latitude'],
  35. self.options['longitude']
  36. )
  37. try:
  38. self._cache_max_age = self.options['cache_max_age']
  39. except KeyError:
  40. self._cache_max_age = 1800
  41. try:
  42. self._temp_precision = self.options['temp_precision']
  43. except KeyError:
  44. self._temp_precision = 0
  45. self.commands = {
  46. 'whats the temperature': self._temperature,
  47. 'whats todays low': self._todays_low,
  48. 'whats todays high': self._todays_high,
  49. 'whats tomorrows high': self._tomorrows_high,
  50. 'whats the humidity': self._relative_humidity,
  51. 'whats the weather': self._current_conditions,
  52. 'whens sunset': self._sunset_time,
  53. 'hoos your weather provider': self._provider_credits
  54. }
  55. self.corpus_strings.update(self.commands)
  56. def _format_temperature(self, temperature, unit='Fahrenheit'):
  57. """Format a temperature for the TTS system"""
  58. return '{:.{prec}f} degrees {}'.format(temperature, unit,
  59. prec=self._temp_precision)
  60. def _temperature(self):
  61. return self._format_temperature(self.cache['currently']['temperature'])
  62. def _todays_low(self):
  63. return self._format_temperature(
  64. self.cache['daily']['data'][0]['temperatureMin'])
  65. def _todays_high(self):
  66. return self._format_temperature(
  67. self.cache['daily']['data'][0]['temperatureMax'])
  68. def _tomorrows_high(self):
  69. return self._format_temperature(
  70. self.cache['daily']['data'][1]['temperatureMax'])
  71. def _relative_humidity(self):
  72. return '{:.0f} percent'.format(
  73. 100 * self.cache['currently']['humidity'])
  74. def _current_conditions(self):
  75. return '{}, {}, relative humidity {}'.format(
  76. self.cache['currently']['summary'],
  77. self._temperature(),
  78. self._relative_humidity())
  79. def _sunset_time(self):
  80. # Get the time in a more useful structure
  81. t = time.localtime(self.cache['daily']['data'][0]['sunsetTime'])
  82. # Format each part of the time to be pronounced nicely
  83. hour = time.strftime('%I', t)
  84. if hour[0] == '0':
  85. hour = hour[1]
  86. minute = time.strftime('%M', t)
  87. if minute[0] == '0':
  88. if minute[1] == '0':
  89. minute = "o'clock"
  90. else:
  91. minute = 'O' + minute[1]
  92. ante_post = time.strftime('%p', t)
  93. if ante_post[0] == 'A':
  94. ante_post = 'AE ' + ante_post[1]
  95. # Put the parts together
  96. return '{} {} {}'.format(hour, minute, ante_post)
  97. def _provider_credits(self):
  98. return 'Powered by Dark Sky, https://darksky.net/poweredby/.'
  99. def _update_cache_if_stale(self):
  100. try:
  101. st = os.stat(self._cache_filename)
  102. except FileNotFoundError:
  103. # If there's no cache file, we need to get one
  104. self._update_cache()
  105. else:
  106. if time.time() - st.st_mtime > self._cache_max_age:
  107. self._update_cache()
  108. def _update_cache(self):
  109. r = requests.get(self._weather_url, stream=True)
  110. if r.status_code == 200:
  111. with open(self._cache_filename, 'wb') as f:
  112. for chunk in r:
  113. f.write(chunk)
  114. def _read_cache(self):
  115. with open(self._cache_filename, 'r') as f:
  116. return json.load(f)
  117. def confidence(self, text):
  118. """Return whether or not the command can be handled"""
  119. return 1 if text in self.corpus_strings else 0
  120. def handle(self, text):
  121. """Speak reasonably up-to-date weather information"""
  122. # Is there a matching command?
  123. if self.confidence(text):
  124. self._update_cache_if_stale()
  125. self.cache = self._read_cache()
  126. self.emit('tts', self.commands[text]())
  127. return True
  128. else:
  129. return False