Rework wunderground module as a backend of i3pystatus.weather
This commit is contained in:
parent
abf5b6ad1c
commit
f3f2b59c5b
@ -1,5 +1,5 @@
|
|||||||
from i3pystatus import IntervalModule
|
from i3pystatus import IntervalModule
|
||||||
from i3pystatus.core.util import user_open, internet, require
|
from i3pystatus.core.util import internet, require
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from urllib.request import urlopen
|
from urllib.request import urlopen
|
||||||
@ -7,19 +7,21 @@ import json
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
GEOLOOKUP_URL = 'http://api.wunderground.com/api/%s/geolookup%s/q/%s.json'
|
GEOLOOKUP_URL = 'http://api.wunderground.com/api/%s/geolookup%s/q/%s.json'
|
||||||
STATION_LOOKUP_URL = 'http://api.wunderground.com/api/%s/conditions/q/%s.json'
|
STATION_QUERY_URL = 'http://api.wunderground.com/api/%s/%s/q/%s.json'
|
||||||
|
|
||||||
|
|
||||||
class Wunderground(IntervalModule):
|
class Wunderground(IntervalModule):
|
||||||
'''
|
'''
|
||||||
This module retrieves weather from the Weather Underground API.
|
This module retrieves weather data using the Weather Underground API.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
A Weather Underground API key is required to use this module, you can
|
A Weather Underground API key is required to use this module, you can
|
||||||
sign up for one for a developer API key free at
|
sign up for a developer API key free at
|
||||||
https://www.wunderground.com/weather/api/
|
https://www.wunderground.com/weather/api/
|
||||||
|
|
||||||
A developer API key is allowed 500 queries per day.
|
A developer API key is allowed 500 queries per day, and no more than 10
|
||||||
|
in a given minute. Therefore, it is recommended to be conservative when
|
||||||
|
setting the update interval.
|
||||||
|
|
||||||
Valid values for ``location_code`` include:
|
Valid values for ``location_code`` include:
|
||||||
|
|
||||||
@ -37,58 +39,60 @@ class Wunderground(IntervalModule):
|
|||||||
|
|
||||||
http://www.wunderground.com/weatherstation/ListStations.asp
|
http://www.wunderground.com/weatherstation/ListStations.asp
|
||||||
|
|
||||||
.. rubric:: Available formatters
|
.. _weather-usage-wunderground:
|
||||||
|
|
||||||
* `{city}` — Location of weather observation
|
.. rubric:: Usage example
|
||||||
* `{conditon}` — Current condition (Rain, Snow, Overcast, etc.)
|
|
||||||
* `{observation_time}` — Time of weather observation (supports strftime format flags)
|
|
||||||
* `{current_temp}` — Current temperature, excluding unit
|
|
||||||
* `{degrees}` — ``°C`` if ``units`` is set to ``metric``, otherwise ``°F``
|
|
||||||
* `{feelslike}` — Wunderground "Feels Like" temperature, excluding unit
|
|
||||||
* `{current_wind}` — Wind speed in mph/kph, excluding unit
|
|
||||||
* `{current_wind_direction}` — Wind direction
|
|
||||||
* `{current_wind_gust}` — Speed of wind gusts in mph/kph, excluding unit
|
|
||||||
* `{pressure_in}` — Barometric pressure (in inches), excluding unit
|
|
||||||
* `{pressure_mb}` — Barometric pressure (in millibars), excluding unit
|
|
||||||
* `{pressure_trend}` — ``+`` (rising) or ``-`` (falling)
|
|
||||||
* `{visibility}` — Visibility in mi/km, excluding unit
|
|
||||||
* `{humidity}` — Current humidity, excluding percentage symbol
|
|
||||||
* `{dewpoint}` — Dewpoint temperature, excluding unit
|
|
||||||
* `{uv_index}` — UV Index
|
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from i3pystatus import Status
|
||||||
|
from i3pystatus.weather import wunderground
|
||||||
|
|
||||||
|
status = Status()
|
||||||
|
|
||||||
|
status.register(
|
||||||
|
'weather',
|
||||||
|
format='{condition} {current_temp}{temp_unit}{icon}[ Hi: {high_temp}] Lo: {low_temp}',
|
||||||
|
colorize=True,
|
||||||
|
backend=wunderground.Wunderground(
|
||||||
|
api_key='dbafe887d56ba4ad',
|
||||||
|
location_code='pws:MAT645',
|
||||||
|
units='imperial',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
status.run()
|
||||||
|
|
||||||
|
See :ref:`here <weather-formatters>` for a list of formatters which can be
|
||||||
|
used.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
interval = 300
|
interval = 300
|
||||||
|
|
||||||
settings = (
|
settings = (
|
||||||
('api_key', 'Weather Underground API key'),
|
('api_key', 'Weather Underground API key'),
|
||||||
('location_code', 'Location code from www.weather.com'),
|
('location_code', 'Location code from wunderground.com'),
|
||||||
('units', 'Celsius (metric) or Fahrenheit (imperial)'),
|
('units', '\'metric\' or \'imperial\''),
|
||||||
('use_pws', 'Set to False to use only airport stations'),
|
('use_pws', 'Set to False to use only airport stations'),
|
||||||
('error_log', 'If set, tracebacks will be logged to this file'),
|
('forecast', 'Set to ``True`` to check forecast (generates one '
|
||||||
'format',
|
'additional API request per weather update). If set to '
|
||||||
|
'``False``, then the ``low_temp`` and ``high_temp`` '
|
||||||
|
'formatters will be set to empty strings.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
required = ('api_key', 'location_code')
|
required = ('api_key', 'location_code')
|
||||||
|
|
||||||
api_key = None
|
api_key = None
|
||||||
location_code = None
|
location_code = None
|
||||||
units = "metric"
|
units = 'metric'
|
||||||
format = "{current_temp}{degrees}"
|
|
||||||
use_pws = True
|
use_pws = True
|
||||||
error_log = None
|
forecast = False
|
||||||
|
|
||||||
|
# These will be set once weather data has been checked
|
||||||
station_id = None
|
station_id = None
|
||||||
forecast_url = None
|
forecast_url = None
|
||||||
|
|
||||||
on_leftclick = 'open_wunderground'
|
@require(internet)
|
||||||
|
|
||||||
def open_wunderground(self):
|
|
||||||
'''
|
|
||||||
Open the forecast URL, if one was retrieved
|
|
||||||
'''
|
|
||||||
if self.forecast_url and self.forecast_url != 'N/A':
|
|
||||||
user_open(self.forecast_url)
|
|
||||||
|
|
||||||
def api_request(self, url):
|
def api_request(self, url):
|
||||||
'''
|
'''
|
||||||
Execute an HTTP POST to the specified URL and return the content
|
Execute an HTTP POST to the specified URL and return the content
|
||||||
@ -106,6 +110,7 @@ class Wunderground(IntervalModule):
|
|||||||
pass
|
pass
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@require(internet)
|
||||||
def geolookup(self):
|
def geolookup(self):
|
||||||
'''
|
'''
|
||||||
Use the location_code to perform a geolookup and find the closest
|
Use the location_code to perform a geolookup and find the closest
|
||||||
@ -148,32 +153,63 @@ class Wunderground(IntervalModule):
|
|||||||
raise Exception('No icao entry for station')
|
raise Exception('No icao entry for station')
|
||||||
self.station_id = 'icao:%s' % nearest_airport
|
self.station_id = 'icao:%s' % nearest_airport
|
||||||
|
|
||||||
def query_station(self):
|
@require(internet)
|
||||||
|
def get_forecast(self):
|
||||||
'''
|
'''
|
||||||
Query a specific station
|
If configured to do so, make an API request to retrieve the forecast
|
||||||
|
data for the configured/queried weather station, and return the low and
|
||||||
|
high temperatures. Otherwise, return two empty strings.
|
||||||
|
'''
|
||||||
|
if self.forecast:
|
||||||
|
query_url = STATION_QUERY_URL % (self.api_key,
|
||||||
|
'forecast',
|
||||||
|
self.station_id)
|
||||||
|
try:
|
||||||
|
response = self.api_request(query_url)['forecast']
|
||||||
|
response = response['simpleforecast']['forecastday'][0]
|
||||||
|
except (KeyError, IndexError, TypeError):
|
||||||
|
raise Exception('No forecast data found for %s' % self.station_id)
|
||||||
|
|
||||||
|
unit = 'celsius' if self.units == 'metric' else 'fahrenheit'
|
||||||
|
low_temp = response.get('low', {}).get(unit, '')
|
||||||
|
high_temp = response.get('high', {}).get(unit, '')
|
||||||
|
return low_temp, high_temp
|
||||||
|
else:
|
||||||
|
return '', ''
|
||||||
|
|
||||||
|
@require(internet)
|
||||||
|
def weather_data(self):
|
||||||
|
'''
|
||||||
|
Query the configured/queried station and return the weather data
|
||||||
'''
|
'''
|
||||||
# If necessary, do a geolookup to set the station_id
|
# If necessary, do a geolookup to set the station_id
|
||||||
self.geolookup()
|
self.geolookup()
|
||||||
|
|
||||||
query_url = STATION_LOOKUP_URL % (self.api_key, self.station_id)
|
query_url = STATION_QUERY_URL % (self.api_key,
|
||||||
|
'conditions',
|
||||||
|
self.station_id)
|
||||||
try:
|
try:
|
||||||
response = self.api_request(query_url)['current_observation']
|
response = self.api_request(query_url)['current_observation']
|
||||||
self.forecast_url = response.pop('ob_url', None)
|
self.forecast_url = response.pop('ob_url', None)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise Exception('No weather data found for %s' % self.station_id)
|
raise Exception('No weather data found for %s' % self.station_id)
|
||||||
|
|
||||||
def _find(key, data=None):
|
low_temp, high_temp = self.get_forecast()
|
||||||
data = data or response
|
|
||||||
return data.get(key, 'N/A')
|
|
||||||
|
|
||||||
if self.units == 'metric':
|
if self.units == 'metric':
|
||||||
temp_unit = 'c'
|
temp_unit = 'c'
|
||||||
speed_unit = 'kph'
|
speed_unit = 'kph'
|
||||||
distance_unit = 'km'
|
distance_unit = 'km'
|
||||||
|
pressure_unit = 'mb'
|
||||||
else:
|
else:
|
||||||
temp_unit = 'f'
|
temp_unit = 'f'
|
||||||
speed_unit = 'mph'
|
speed_unit = 'mph'
|
||||||
distance_unit = 'mi'
|
distance_unit = 'mi'
|
||||||
|
pressure_unit = 'in'
|
||||||
|
|
||||||
|
def _find(key, data=None):
|
||||||
|
data = data or response
|
||||||
|
return data.get(key, 'N/A')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
observation_time = int(_find('observation_epoch'))
|
observation_time = int(_find('observation_epoch'))
|
||||||
@ -185,36 +221,20 @@ class Wunderground(IntervalModule):
|
|||||||
condition=_find('weather'),
|
condition=_find('weather'),
|
||||||
observation_time=datetime.fromtimestamp(observation_time),
|
observation_time=datetime.fromtimestamp(observation_time),
|
||||||
current_temp=_find('temp_' + temp_unit),
|
current_temp=_find('temp_' + temp_unit),
|
||||||
|
low_temp=low_temp,
|
||||||
|
high_temp=high_temp,
|
||||||
|
temp_unit='°' + temp_unit.upper(),
|
||||||
feelslike=_find('feelslike_' + temp_unit),
|
feelslike=_find('feelslike_' + temp_unit),
|
||||||
current_wind=_find('wind_' + speed_unit),
|
dewpoint=_find('dewpoint_' + temp_unit),
|
||||||
current_wind_direction=_find('wind_dir'),
|
wind_speed=_find('wind_' + speed_unit),
|
||||||
current_wind_gust=_find('wind_gust_' + speed_unit),
|
wind_unit=speed_unit,
|
||||||
pressure_in=_find('pressure_in'),
|
wind_direction=_find('wind_dir'),
|
||||||
pressure_mb=_find('pressure_mb'),
|
wind_gust=_find('wind_gust_' + speed_unit),
|
||||||
|
pressure=_find('pressure_' + pressure_unit),
|
||||||
|
pressure_unit=pressure_unit,
|
||||||
pressure_trend=_find('pressure_trend'),
|
pressure_trend=_find('pressure_trend'),
|
||||||
visibility=_find('visibility_' + distance_unit),
|
visibility=_find('visibility_' + distance_unit),
|
||||||
|
visibility_unit=distance_unit,
|
||||||
humidity=_find('relative_humidity').rstrip('%'),
|
humidity=_find('relative_humidity').rstrip('%'),
|
||||||
dewpoint=_find('dewpoint_' + temp_unit),
|
|
||||||
uv_index=_find('uv'),
|
uv_index=_find('uv'),
|
||||||
)
|
)
|
||||||
|
|
||||||
@require(internet)
|
|
||||||
def run(self):
|
|
||||||
try:
|
|
||||||
result = self.query_station()
|
|
||||||
except Exception as exc:
|
|
||||||
if self.error_log:
|
|
||||||
import traceback
|
|
||||||
with open(self.error_log, 'a') as f:
|
|
||||||
f.write('%s : An exception was raised:\n' %
|
|
||||||
datetime.isoformat(datetime.now()))
|
|
||||||
f.write(''.join(traceback.format_exc()))
|
|
||||||
f.write(80 * '-' + '\n')
|
|
||||||
raise
|
|
||||||
|
|
||||||
result['degrees'] = '°%s' % ('C' if self.units == 'metric' else 'F')
|
|
||||||
|
|
||||||
self.output = {
|
|
||||||
"full_text": self.format.format(**result),
|
|
||||||
# "color": self.color # TODO: add some sort of color effect
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user