|
@@ -30,7 +30,7 @@ import time
|
30
|
30
|
|
31
|
31
|
import requests
|
32
|
32
|
|
33
|
|
-from .pluginbase import PluginBase
|
|
33
|
+from . import PluginBase, Handler
|
34
|
34
|
|
35
|
35
|
|
36
|
36
|
class Plugin(PluginBase):
|
|
@@ -56,51 +56,73 @@ class Plugin(PluginBase):
|
56
|
56
|
self._temp_precision = 0
|
57
|
57
|
|
58
|
58
|
self.commands = {
|
59
|
|
- 'whats the temperature': self._temperature,
|
60
|
|
- 'whats todays low': self._todays_low,
|
61
|
|
- 'whats todays high': self._todays_high,
|
62
|
|
- 'whats tomorrows high': self._tomorrows_high,
|
63
|
|
- 'whats the humidity': self._relative_humidity,
|
64
|
|
- 'whats the weather': self._current_conditions,
|
65
|
|
- 'whens sunset': self._sunset_time,
|
66
|
|
- 'hoos your weather provider': self._provider_credits
|
|
59
|
+ 'whats the temperature': DarkSkyTemperatureHandler,
|
|
60
|
+ 'whats todays high': DarkSkyTodaysHighHandler,
|
|
61
|
+ 'whats todays low': DarkSkyTodaysLowHandler,
|
|
62
|
+ 'whats tomorrows high': DarkSkyTomorrowsHighHandler,
|
|
63
|
+ 'whats the humidity': DarkSkyHumidityHandler,
|
|
64
|
+ 'whats the weather': DarkSkyCurrentConditionsHandler,
|
|
65
|
+ 'whens sunset': DarkSkySunsetHandler,
|
|
66
|
+ 'hoos your weather provider': DarkSkyProviderHandler
|
67
|
67
|
}
|
68
|
68
|
|
69
|
69
|
self.corpus_strings.update(self.commands)
|
70
|
70
|
|
71
|
|
- def _format_temperature(self, temperature, unit='Fahrenheit'):
|
72
|
|
- """Format a temperature for the TTS system"""
|
73
|
|
- return '{:.{prec}f} degrees {}'.format(temperature, unit,
|
74
|
|
- prec=self._temp_precision)
|
|
71
|
+ def get_handler(self, text):
|
|
72
|
+ """Return a handler if a recognized command is heard"""
|
|
73
|
+ if text in self.corpus_strings:
|
|
74
|
+ return self.commands[text](1, self._cache_filename,
|
|
75
|
+ self._weather_url, self._cache_max_age,
|
|
76
|
+ self._temp_precision)
|
|
77
|
+ else:
|
|
78
|
+ return None
|
75
|
79
|
|
76
|
|
- def _temperature(self):
|
77
|
|
- return self._format_temperature(self.cache['currently']['temperature'])
|
78
|
80
|
|
79
|
|
- def _todays_low(self):
|
80
|
|
- return self._format_temperature(
|
81
|
|
- self.cache['daily']['data'][0]['temperatureMin'])
|
|
81
|
+class DarkSkyHandler(Handler):
|
|
82
|
+ """Base class for Dark Sky plugin handlers"""
|
82
|
83
|
|
83
|
|
- def _todays_high(self):
|
84
|
|
- return self._format_temperature(
|
85
|
|
- self.cache['daily']['data'][0]['temperatureMax'])
|
|
84
|
+ def __init__(self, confidence, cache_filename, url, cache_max_age,
|
|
85
|
+ temp_precision):
|
|
86
|
+ super().__init__(confidence)
|
86
|
87
|
|
87
|
|
- def _tomorrows_high(self):
|
88
|
|
- return self._format_temperature(
|
89
|
|
- self.cache['daily']['data'][1]['temperatureMax'])
|
|
88
|
+ self._cache_filename = cache_filename
|
|
89
|
+ self._url = url
|
|
90
|
+ self._cache_max_age = cache_max_age
|
|
91
|
+ self._temp_precision = temp_precision
|
90
|
92
|
|
91
|
|
- def _relative_humidity(self):
|
92
|
|
- return '{:.0f} percent'.format(
|
93
|
|
- 100 * self.cache['currently']['humidity'])
|
|
93
|
+ def _update_cache_if_stale(self):
|
|
94
|
+ try:
|
|
95
|
+ st = os.stat(self._cache_filename)
|
|
96
|
+ except FileNotFoundError:
|
|
97
|
+ # If there's no cache file, we need to get one
|
|
98
|
+ self._update_cache()
|
|
99
|
+ else:
|
|
100
|
+ if time.time() - st.st_mtime > self._cache_max_age:
|
|
101
|
+ self._update_cache()
|
94
|
102
|
|
95
|
|
- def _current_conditions(self):
|
96
|
|
- return '{}, {}, relative humidity {}'.format(
|
97
|
|
- self.cache['currently']['summary'],
|
98
|
|
- self._temperature(),
|
99
|
|
- self._relative_humidity())
|
|
103
|
+ def _update_cache(self):
|
|
104
|
+ r = requests.get(self._url, stream=True)
|
|
105
|
+ if r.status_code == 200:
|
|
106
|
+ with open(self._cache_filename, 'wb') as f:
|
|
107
|
+ for chunk in r:
|
|
108
|
+ f.write(chunk)
|
100
|
109
|
|
101
|
|
- def _sunset_time(self):
|
102
|
|
- # Get the time in a more useful structure
|
103
|
|
- t = time.localtime(self.cache['daily']['data'][0]['sunsetTime'])
|
|
110
|
+ def _read_cache(self):
|
|
111
|
+ with open(self._cache_filename, 'r') as f:
|
|
112
|
+ return json.load(f)
|
|
113
|
+
|
|
114
|
+ def _format_temperature(self, temperature, unit='Fahrenheit'):
|
|
115
|
+ """Format a temperature for the TTS system"""
|
|
116
|
+ return '{:.{prec}f} degrees {}'.format(temperature, unit,
|
|
117
|
+ prec=self._temp_precision)
|
|
118
|
+
|
|
119
|
+ def _format_percent(self, value):
|
|
120
|
+ """Format a percentage value for the TTS system"""
|
|
121
|
+ return '{:.0f} percent'.format(100 * value)
|
|
122
|
+
|
|
123
|
+ def _format_time(self, epoch_time):
|
|
124
|
+ """Format a time in seconds since the epoch for the TTS system"""
|
|
125
|
+ t = time.localtime(epoch_time)
|
104
|
126
|
|
105
|
127
|
# Format each part of the time to be pronounced nicely
|
106
|
128
|
hour = time.strftime('%I', t)
|
|
@@ -121,41 +143,85 @@ class Plugin(PluginBase):
|
121
|
143
|
# Put the parts together
|
122
|
144
|
return '{} {} {}'.format(hour, minute, ante_post)
|
123
|
145
|
|
124
|
|
- def _provider_credits(self):
|
125
|
|
- return 'Powered by Dark Sky, https:// dark sky .net/ powered by/.'
|
|
146
|
+ def __call__(self, tts):
|
|
147
|
+ """Update the cache if necessary and load it"""
|
|
148
|
+ self._update_cache_if_stale()
|
|
149
|
+ self.cache = self._read_cache()
|
126
|
150
|
|
127
|
|
- def _update_cache_if_stale(self):
|
128
|
|
- try:
|
129
|
|
- st = os.stat(self._cache_filename)
|
130
|
|
- except FileNotFoundError:
|
131
|
|
- # If there's no cache file, we need to get one
|
132
|
|
- self._update_cache()
|
133
|
|
- else:
|
134
|
|
- if time.time() - st.st_mtime > self._cache_max_age:
|
135
|
|
- self._update_cache()
|
136
|
151
|
|
137
|
|
- def _update_cache(self):
|
138
|
|
- r = requests.get(self._weather_url, stream=True)
|
139
|
|
- if r.status_code == 200:
|
140
|
|
- with open(self._cache_filename, 'wb') as f:
|
141
|
|
- for chunk in r:
|
142
|
|
- f.write(chunk)
|
|
152
|
+class DarkSkyTemperatureHandler(DarkSkyHandler):
|
|
153
|
+ """Handler to speak the current temperature"""
|
143
|
154
|
|
144
|
|
- def _read_cache(self):
|
145
|
|
- with open(self._cache_filename, 'r') as f:
|
146
|
|
- return json.load(f)
|
|
155
|
+ def __call__(self, tts):
|
|
156
|
+ """Speak the current temperature"""
|
|
157
|
+ super().__call__(tts)
|
|
158
|
+ tts(self._format_temperature(self.cache['currently']['temperature']))
|
147
|
159
|
|
148
|
|
- def confidence(self, text):
|
149
|
|
- """Return whether or not the command can be handled"""
|
150
|
|
- return 1 if text in self.corpus_strings else 0
|
151
|
|
-
|
152
|
|
- def handle(self, text):
|
153
|
|
- """Speak reasonably up-to-date weather information"""
|
154
|
|
- # Is there a matching command?
|
155
|
|
- if self.confidence(text):
|
156
|
|
- self._update_cache_if_stale()
|
157
|
|
- self.cache = self._read_cache()
|
158
|
|
- self.emit('tts', self.commands[text]())
|
159
|
|
- return True
|
160
|
|
- else:
|
161
|
|
- return False
|
|
160
|
+
|
|
161
|
+class DarkSkyTodaysHighHandler(DarkSkyHandler):
|
|
162
|
+ """Handler to speak today's high temperature"""
|
|
163
|
+
|
|
164
|
+ def __call__(self, tts):
|
|
165
|
+ """Speak today's high"""
|
|
166
|
+ super().__call__(tts)
|
|
167
|
+ tts(self._format_temperature(
|
|
168
|
+ self.cache['daily']['data'][0]['temperatureMax']))
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+class DarkSkyTodaysLowHandler(DarkSkyHandler):
|
|
172
|
+ """Handler to speak today's low temperature"""
|
|
173
|
+
|
|
174
|
+ def __call__(self, tts):
|
|
175
|
+ """Speak today's low"""
|
|
176
|
+ super().__call__(tts)
|
|
177
|
+ tts(self._format_temperature(
|
|
178
|
+ self.cache['daily']['data'][0]['temperatureMin']))
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+class DarkSkyTomorrowsHighHandler(DarkSkyHandler):
|
|
182
|
+ """Handler to speak tomorrow's high temperature"""
|
|
183
|
+
|
|
184
|
+ def __call__(self, tts):
|
|
185
|
+ """Speak tomorrow's high"""
|
|
186
|
+ super().__call__(tts)
|
|
187
|
+ tts(self._format_temperature(
|
|
188
|
+ self.cache['daily']['data'][1]['temperatureMax']))
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+class DarkSkyHumidityHandler(DarkSkyHandler):
|
|
192
|
+ """Handler to speak the current relative humidity"""
|
|
193
|
+
|
|
194
|
+ def __call__(self, tts):
|
|
195
|
+ """Speak the current relative humidity"""
|
|
196
|
+ super().__call__(tts)
|
|
197
|
+ tts(self._format_percent(self.cache['currently']['humidity']))
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+class DarkSkyCurrentConditionsHandler(DarkSkyHandler):
|
|
201
|
+ """Handler to speak the current weather conditions"""
|
|
202
|
+
|
|
203
|
+ def __call__(self, tts):
|
|
204
|
+ """Speak the current weather conditions"""
|
|
205
|
+ super().__call__(tts)
|
|
206
|
+ tts('{}, {}, relative humidity {}'.format(
|
|
207
|
+ self.cache['currently']['summary'],
|
|
208
|
+ self._format_temperature(self.cache['currently']['temperature']),
|
|
209
|
+ self._format_percent(self.cache['currently']['humidity'])))
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+class DarkSkySunsetHandler(DarkSkyHandler):
|
|
213
|
+ """Handler to speak the time of sunset"""
|
|
214
|
+
|
|
215
|
+ def __call__(self, tts):
|
|
216
|
+ """Speak the time of sunset"""
|
|
217
|
+ super().__call__(tts)
|
|
218
|
+ tts(self._format_time(self.cache['daily']['data'][0]['sunsetTime']))
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+class DarkSkyProviderHandler(DarkSkyHandler):
|
|
222
|
+ """Handler to speak information about the weather provider"""
|
|
223
|
+
|
|
224
|
+ def __call__(self, tts):
|
|
225
|
+ """Speak information about the weather provider"""
|
|
226
|
+ # No need for super since this doesn't need any weather information
|
|
227
|
+ tts('Powered by Dark Sky, https:// dark sky .net/ powered by/.')
|