summaryrefslogtreecommitdiffstats
path: root/powerline/segments/common/wthr.py
diff options
context:
space:
mode:
Diffstat (limited to 'powerline/segments/common/wthr.py')
-rw-r--r--powerline/segments/common/wthr.py234
1 files changed, 234 insertions, 0 deletions
diff --git a/powerline/segments/common/wthr.py b/powerline/segments/common/wthr.py
new file mode 100644
index 0000000..2c54cca
--- /dev/null
+++ b/powerline/segments/common/wthr.py
@@ -0,0 +1,234 @@
+# vim:fileencoding=utf-8:noet
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+import json
+from collections import namedtuple
+
+from powerline.lib.url import urllib_read, urllib_urlencode
+from powerline.lib.threaded import KwThreadedSegment
+from powerline.segments import with_docstring
+
+
+_WeatherKey = namedtuple('Key', 'location_query weather_api_key')
+
+
+# XXX Warning: module name must not be equal to the segment name as long as this
+# segment is imported into powerline.segments.common module.
+
+
+# Weather condition code descriptions available at
+# https://openweathermap.org/weather-conditions
+weather_conditions_codes = {
+ 200: ('stormy',),
+ 201: ('stormy',),
+ 202: ('stormy',),
+ 210: ('stormy',),
+ 211: ('stormy',),
+ 212: ('stormy',),
+ 221: ('stormy',),
+ 230: ('stormy',),
+ 231: ('stormy',),
+ 232: ('stormy',),
+ 300: ('rainy',),
+ 301: ('rainy',),
+ 302: ('rainy',),
+ 310: ('rainy',),
+ 311: ('rainy',),
+ 312: ('rainy',),
+ 313: ('rainy',),
+ 314: ('rainy',),
+ 321: ('rainy',),
+ 500: ('rainy',),
+ 501: ('rainy',),
+ 502: ('rainy',),
+ 503: ('rainy',),
+ 504: ('rainy',),
+ 511: ('snowy',),
+ 520: ('rainy',),
+ 521: ('rainy',),
+ 522: ('rainy',),
+ 531: ('rainy',),
+ 600: ('snowy',),
+ 601: ('snowy',),
+ 602: ('snowy',),
+ 611: ('snowy',),
+ 612: ('snowy',),
+ 613: ('snowy',),
+ 615: ('snowy',),
+ 616: ('snowy',),
+ 620: ('snowy',),
+ 621: ('snowy',),
+ 622: ('snowy',),
+ 701: ('foggy',),
+ 711: ('foggy',),
+ 721: ('foggy',),
+ 731: ('foggy',),
+ 741: ('foggy',),
+ 751: ('foggy',),
+ 761: ('foggy',),
+ 762: ('foggy',),
+ 771: ('foggy',),
+ 781: ('foggy',),
+ 800: ('sunny',),
+ 801: ('cloudy',),
+ 802: ('cloudy',),
+ 803: ('cloudy',),
+ 804: ('cloudy',),
+}
+
+weather_conditions_icons = {
+ 'day': 'DAY',
+ 'blustery': 'WIND',
+ 'rainy': 'RAIN',
+ 'cloudy': 'CLOUDS',
+ 'snowy': 'SNOW',
+ 'stormy': 'STORM',
+ 'foggy': 'FOG',
+ 'sunny': 'SUN',
+ 'night': 'NIGHT',
+ 'windy': 'WINDY',
+ 'not_available': 'NA',
+ 'unknown': 'UKN',
+}
+
+temp_conversions = {
+ 'C': lambda temp: temp - 273.15,
+ 'F': lambda temp: (temp * 9 / 5) - 459.67,
+ 'K': lambda temp: temp,
+}
+
+# Note: there are also unicode characters for units: ℃, ℉ and K
+temp_units = {
+ 'C': '°C',
+ 'F': '°F',
+ 'K': 'K',
+}
+
+
+class WeatherSegment(KwThreadedSegment):
+ interval = 600
+ default_location = None
+ location_urls = {}
+ weather_api_key = "fbc9549d91a5e4b26c15be0dbdac3460"
+
+ @staticmethod
+ def key(location_query=None, **kwargs):
+ try:
+ weather_api_key = kwargs["weather_api_key"]
+ except KeyError:
+ weather_api_key = WeatherSegment.weather_api_key
+ return _WeatherKey(location_query, weather_api_key)
+
+ def get_request_url(self, weather_key):
+ try:
+ return self.location_urls[weather_key]
+ except KeyError:
+ query_data = {
+ "appid": weather_key.weather_api_key
+ }
+ location_query = weather_key.location_query
+ if location_query is None:
+ location_data = json.loads(urllib_read('https://freegeoip.app/json/'))
+ query_data["lat"] = location_data["latitude"]
+ query_data["lon"] = location_data["longitude"]
+ else:
+ query_data["q"] = location_query
+ self.location_urls[location_query] = url = (
+ "https://api.openweathermap.org/data/2.5/weather?" +
+ urllib_urlencode(query_data))
+ return url
+
+ def compute_state(self, weather_key):
+ url = self.get_request_url(weather_key)
+ raw_response = urllib_read(url)
+ if not raw_response:
+ self.error('Failed to get response')
+ return None
+
+ response = json.loads(raw_response)
+ try:
+ condition = response['weather'][0]
+ condition_code = int(condition['id'])
+ temp = float(response['main']['temp'])
+ except (KeyError, ValueError):
+ self.exception('OpenWeatherMap returned malformed or unexpected response: {0}', repr(raw_response))
+ return None
+
+ try:
+ icon_names = weather_conditions_codes[condition_code]
+ except IndexError:
+ icon_names = ('unknown',)
+ self.error('Unknown condition code: {0}', condition_code)
+
+ return (temp, icon_names)
+
+ def render_one(self, weather, icons=None, unit='C', temp_format=None, temp_coldest=-30, temp_hottest=40, **kwargs):
+ if not weather:
+ return None
+
+ temp, icon_names = weather
+
+ for icon_name in icon_names:
+ if icons:
+ if icon_name in icons:
+ icon = icons[icon_name]
+ break
+ else:
+ icon = weather_conditions_icons[icon_names[-1]]
+
+ temp_format = temp_format or ('{temp:.0f}' + temp_units[unit])
+ converted_temp = temp_conversions[unit](temp)
+ if converted_temp <= temp_coldest:
+ gradient_level = 0
+ elif converted_temp >= temp_hottest:
+ gradient_level = 100
+ else:
+ gradient_level = (converted_temp - temp_coldest) * 100.0 / (temp_hottest - temp_coldest)
+ groups = ['weather_condition_' + icon_name for icon_name in icon_names] + ['weather_conditions', 'weather']
+ return [
+ {
+ 'contents': icon + ' ',
+ 'highlight_groups': groups,
+ 'divider_highlight_group': 'background:divider',
+ },
+ {
+ 'contents': temp_format.format(temp=converted_temp),
+ 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'],
+ 'divider_highlight_group': 'background:divider',
+ 'gradient_level': gradient_level,
+ },
+ ]
+
+
+weather = with_docstring(WeatherSegment(),
+'''Return weather from OpenWeatherMaps.
+
+Uses GeoIP lookup from https://freegeoip.app to automatically determine
+your current location. This should be changed if you’re in a VPN or if your
+IP address is registered at another location.
+
+Returns a list of colorized icon and temperature segments depending on
+weather conditions.
+
+:param str unit:
+ temperature unit, can be one of ``F``, ``C`` or ``K``
+:param str location_query:
+ location query for your current location, e.g. ``oslo, norway``
+:param dict icons:
+ dict for overriding default icons, e.g. ``{'heavy_snow' : u'❆'}``
+:param str temp_format:
+ format string, receives ``temp`` as an argument. Should also hold unit.
+:param float temp_coldest:
+ coldest temperature. Any temperature below it will have gradient level equal
+ to zero.
+:param float temp_hottest:
+ hottest temperature. Any temperature above it will have gradient level equal
+ to 100. Temperatures between ``temp_coldest`` and ``temp_hottest`` receive
+ gradient level that indicates relative position in this interval
+ (``100 * (cur-coldest) / (hottest-coldest)``).
+
+Divider highlight group used: ``background:divider``.
+
+Highlight groups used: ``weather_conditions`` or ``weather``, ``weather_temp_gradient`` (gradient) or ``weather``.
+Also uses ``weather_conditions_{condition}`` for all weather conditions supported by OpenWeatherMap.
+''')