Browse Source

Added a preliminary Dark Sky weather plugin

It has the same functionality as my old shell script, but now it's
written in Python.  The code is far from the most elegant it could be,
but it's a decent example of how the new plugin API can be used for
something practical.
Clara Hobbs 7 years ago
parent
commit
64c55bc61e
1 changed files with 139 additions and 0 deletions
  1. 139
    0
      kayleevc/plugins/darksky.py

+ 139
- 0
kayleevc/plugins/darksky.py View File

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

Loading…
Cancel
Save