From: Duncan W. <du...@fr...> - 2007-09-25 16:58:52
|
Author: duncan Date: Tue Sep 25 12:58:53 2007 New Revision: 9900 Log: The real files Added: branches/rel-1/freevo/src/plugins/oneclick.py (contents, props changed) branches/rel-1/freevo/src/plugins/weatherdata.py (contents, props changed) Added: branches/rel-1/freevo/src/plugins/oneclick.py ============================================================================== --- (empty file) +++ branches/rel-1/freevo/src/plugins/oneclick.py Tue Sep 25 12:58:53 2007 @@ -0,0 +1,1096 @@ +# -*- coding: iso-8859-1 -*- +# ----------------------------------------------------------------------- +# oneclick.py - a plugin to obtain detailed weather forecast information +# ----------------------------------------------------------------------- +# $Id$ +# +# Notes: +# +# Todo: +# X pull down weather on demand MENU_SELECT (need to fix popup behavior) +# X Ability to specify custom location name in ONECLICK_LOCATIONS +# - get location name back onto details screen +# - i18n support +# - a freevo helper to grab weather data behind the scenes +# +# activate: +# +# plugin.activate('oneclick', level=45) +# ONECLICK_LOCATIONS = [ ("USNC0559", "", "Home sweet home", 0) ] +# +# ----------------------------------------------------------------------- +# Freevo - A Home Theater PC framework +# Copyright (C) 2003 Krister Lagerstrom, et al. +# Please see the file freevo/Docs/CREDITS for a complete list of authors. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MER- +# CHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# ----------------------------------------------------------------------- + +#python modules +import os, stat, re, copy +import sys + +# date/time +import time + +#regular expression +import re + +# rdf modules +from xml.dom.ext.reader import Sax2 +import urllib, urllib2 + +import cElementTree as ET + +#freevo modules +import config, menu, rc, plugin, skin, osd, util +from gui.PopupBox import PopupBox +from item import Item + +from weatherdata import WeatherData + +GUI = True +if __name__ == '__main__': + GUI = False + +#get the singletons so we get skin info and access the osd +skin = skin.get_singleton() +osd = osd.get_singleton() + +#check every 1 hour +WEATHER_AGE = 3600 +WEATHER_AGE = 3600 * 24 +WEATHER_DIR = os.path.join(config.SHARE_DIR, 'images', 'weather') +WEATHER_DIR = '/sources/svn/freevo-1.x/testing/Duncan/weather/1click/images' + +#FIXME Not sure that all of these are correctly named +WEATHER_ICONS = { +# icon twc.com image fallback + '0': ( 'twc/0.png', 'thunderstorm.png', 'thunshowers.png'), + '1': ( 'twc/1.png', 'thunderstorm.png', 'thunshowers.png'), + '2': ( 'twc/2.png', 'thunderstorm.png', 'thunshowers.png'), + '3': ( 'twc/3.png', 'thunderstorm.png', 'thunshowers.png'), + '4': ( 'twc/4.png', 'thunderstorm.png', 'thunshowers.png'), + '5': ( 'twc/5.png', 'sleet.png', 'rainsnow.png'), + '6': ( 'twc/6.png', 'hail.png', 'rainsnow.png'), + '7': ( 'twc/7.png', 'mixed.png', 'rainsnow.png'), + '8': ( 'twc/8.png', 'freezingfog.png', 'fog.png'), + '9': ( 'twc/9.png', 'drizzle.png', 'fog.png'), + '10': ('twc/10.png', 'freezingrain.png', 'rainsnow.png'), + '11': ('twc/11.png', 'lightrain.png', 'lshowers.png'), + '12': ('twc/12.png', 'rain.png', 'showers.png'), + '13': ('twc/13.png', 'lightsnowshowers.png', 'flurries.png'), + '14': ('twc/14.png', 'mediumsnowshowers.png', 'flurries.png'), + '15': ('twc/15.png', 'freshsnow.png', 'flurries.png'), + '16': ('twc/16.png', 'snow.png', 'flurries.png'), + '17': ('twc/17.png', 'thunderstorm.png', 'thunshowers.png'), + '18': ('twc/18.png', 'drizzle.png', 'showers.png'), + '19': ('twc/19.png', 'dust.png', 'fog.png'), + '20': ('twc/20.png', 'mist.png', 'fog.png'), + '21': ('twc/21.png', 'haze.png', 'fog.png'), + '22': ('twc/22.png', 'smoke.png', 'fog.png'), + '23': ('twc/23.png', 'windy.png', 'pcloudy.png'), + '24': ('twc/24.png', 'windy.png', 'pcloudy.png'), + '25': ('twc/25.png', 'freezing.png', 'pcloudy.png'), + '26': ('twc/26.png', 'cloudy-n.png', 'pcloudy.png'), + '27': ('twc/27.png', 'mostlycloudy-n.png', 'mcloudy.png'), + '28': ('twc/28.png', 'mostlycloudy.png', 'mcloudy.png'), + '29': ('twc/29.png', 'partlycloudy-n.png', 'pcloudy.png'), + '30': ('twc/30.png', 'partlycloudy.png', 'pcloudy.png'), + '31': ('twc/31.png', 'clear-n.png', 'sunny.png'), + '32': ('twc/32.png', 'sunny.png', 'sunny.png'), + '33': ('twc/33.png', 'mostlyclear.png', 'sunny.png'), + '34': ('twc/34.png', 'mostlysunny.png', 'sunny.png'), + '35': ('twc/35.png', 'thunderstorm.png', 'thunshowers.png'), + '36': ('twc/36.png', 'hotandsunny.png', 'sunny.png'), + '37': ('twc/37.png', 'isolatedthunderstorms.png', 'thunshowers.png'), + '38': ('twc/38.png', 'scatteredthunderstorms.png', 'thunshowers.png'), + '39': ('twc/39.png', 'sunnyintervals.png', 'showers.png'), + '40': ('twc/40.png', 'heavyrain.png', 'showers.png'), + '41': ('twc/41.png', 'snowshowers.png', 'snowshow.png'), + '42': ('twc/42.png', 'snow.png', 'rainsnow.png'), + '43': ('twc/43.png', 'snow.png', 'rainsnow.png'), + '44': ('twc/44.png', 'na.png', 'unknown.png'), + '45': ('twc/45.png', 'rainshowers-n.png', 'rainsnow.png'), + '46': ('twc/46.png', 'snowshowers-n.png', 'snowshow.png'), + '47': ('twc/47.png', 'scatteredthunderstorms-n.png', 'thunshowers.png'), + 'na': ('twc/na.png', 'na.png', 'unknown.png'), +} + + +def wget(url): + ''' get a file from the url ''' + _debug_('wget(%s)' % (url), 2) + txdata = None + txheaders = { + 'User-Agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.7) Gecko/20070914 Firefox/2.0.0.7' + } + req = urllib2.Request(url, txdata, txheaders) + try: + t1 = time.time() + response = urllib2.urlopen(req) + try: + data = response.read() + finally: + response.close() + t2 = time.time() + if response.msg == 'OK': + return data + _debug_('Downloaded "%s" in %.1f seconds' % (url, t2 - t1)) + except urllib2.HTTPError, error: + print 'getting %r failed: %s' % (url, error) + except ValueError, error: + try: + fd = open(url) + data = fd.read() + fd.close() + return data + except: + print 'invalid url %r failed: %s' % (url, error) + return None + + +def toCelcius(fTemp): + _debug_('toCelcius(fTemp)', 2) + try: + tTemp = float (fTemp ) + except ValueError: + tTemp = 0.0 + nTemp = (5.0/9.0)*(tTemp - 32.0) + return "%d" % (nTemp,) + +def toKilometers(miles): + _debug_('toKilometers(miles)', 2) + try: + tTemp = float(miles) + except ValueError: + tTemp = 0.0 + nTemp = tTemp*1.6 + return "%d" % (int(nTemp),) + +def toBarometer(baro): + _debug_('toBarometer(baro)', 2) + try: + tTemp = float(baro) + except ValueError: + tTemp = 0.0 + nTemp = tTemp*3.386 + return "%.1f" % (nTemp,) + + +class PluginInterface(plugin.MainMenuPlugin): + ''' + A plugin to obtain more detailed weather forecast information + + To activate, put the following lines in local_conf.py: + + plugin.activate('oneclick', level=45) + ONECLICK_LOCATIONS = [ + ("<loc>", [metric], [mapuri], [location name]), + ("<loc>", [metric], [mapuri], [location name]), + ... + ] + + where: + <loc> is a zipcode or an airport code + [metric] (1 == convert to SI Units; 0 == do not convert) + [mapuri] is the map's url, doesn't parse the page for a map url + [location name] is a custom name you wish to use for this location + ''' + def __init__(self): + ''' + ''' + _debug_('PluginInterface.__init__()', 2) + if not hasattr(config, 'ONECLICK_LOCATIONS'): + self.reason = 'ONECLICK_LOCATIONS not defined' + return + plugin.MainMenuPlugin.__init__(self) + + def config(self): + ''' + ''' + _debug_('config()', 2) + return [ + ('ONECLICK_LOCATIONS', [('USNC0559', 0)], 'Location codes for current conditions and forecasts'), + ('ONECLICK_URL_CURC', 'http://ff.1click.weather.com/weather/local/%s?cc=*%s', 'Current Conditions URL'), + ('ONECLICK_URL_DAYF', 'http://ff.1click.weather.com/weather/local/%s?dayf=5%s', 'Day Forecast URL'), + ('ONECLICK_URL_ELOC', 'http://ff.1click.weather.com/weather/local/%s?eloc=st', 'Extended Location URL'), + ('ONECLICK_URL_MAP', 'http://www.weather.com/weather/map/%s?from=LAPmaps', 'Radar Map URL') + ] + + def items(self, parent): + ''' + ''' + _debug_('items(self, parent)', 2) + return [ WeatherMainMenu(parent) ] + + +class WeatherItem(Item): + ''' + Item for the menu for one feed + ''' + def __init__(self, parent, location): + _debug_('WeatherItem.__init__(parent=%r, location=%r)' % (parent, location), 2) + Item.__init__(self, parent) + + self.parent = parent + + # Flag to indicate whether this item is able to be displayed + self.error = 0 + + self.location = None + self.ismetric = False + self.name = None + self.city = None + self.state = None + self.tm = None + self.latitude = None + self.longitude = None + self.sunrise = None + self.sunset = None + + self.unit_t = None + self.unit_d = None + self.unit_s = None + self.unit_p = None + self.unit_r = None + self.country = None + + self.updated = 0.0 + self.observation_station = None + self.temperature = None + self.feeling = None + self.current_conditions = None + self.icon = None + self.pressure = None + self.pressure_change = None + self.wind_speed = None + self.wind_direction = None + self.humidity = None + self.visibility = None + self.uv_index = None + self.uv_type = None + self.dew_point = None + self.moon_icon = None + self.moon_phase = None + + self.description = None + self.forecastData = None + self.pastTime = 0 + self.date = [] + self.weatherIcon = [] + self.highTemp = [] + self.lowTemp = [] + self.weatherType = [] + self.wdata = [] + self.popupParam = None + self.mapuri = None + + if isinstance(location, tuple): + self.location = location[0] + if len(location) > 1: + self.ismetric = bool(location[1]) + if len(location) > 2: + self.mapuri = str(location[2]) + if len(location) > 3: + self.name = str(location[3]) + else: + self.location = location + self.ismetric = False + + self.popupParam = Unicode(self.location) + if self.name: + self.popupParam = Unicode(self.name) + + self.units = self.ismetric and '&unit=m' or '' + self.url_curc = config.ONECLICK_URL_CURC % (urllib.quote(self.location), self.units) + self.url_dayf = config.ONECLICK_URL_DAYF % (urllib.quote(self.location), self.units) + self.url_eloc = config.ONECLICK_URL_ELOC % (urllib.quote(self.location)) + self.mapurl = config.ONECLICK_URL_MAP % (self.location,) + self.weatherData = None + self.weatherMapData = None + + self.cacheDir = '%s/weather_%s' % (config.FREEVO_CACHEDIR, self.location) + self.cacheElocation = '%s/location.pickle' % (self.cacheDir) + self.cacheCurrent = '%s/current.pickle' % (self.cacheDir) + self.cacheForecast = '%s/forecast.pickle' % (self.cacheDir) + self.mapFile = '%s/map.jpeg' % (self.cacheDir) + self.mapPage1 = '%s/mappage1.html' % (self.cacheDir) + self.mapPage2 = '%s/mappage2.html' % (self.cacheDir) + if not os.path.isdir(self.cacheDir): + os.mkdir(self.cacheDir, stat.S_IMODE(os.stat(config.FREEVO_CACHEDIR)[stat.ST_MODE])) + self.last_update = 0 + + #get forecast data + self.getForecast() + + def getForecast(self, force=0): + '''grab the forecast, updating for the website if needed''' + _debug_('getForecast(force=%s)' % (force), 2) + + # check cache + try: + if force or self.needsRefresh(): + self.updateData() + self.updateMap() + else: + self.loadFromCache() + except IOError, e: + self.error = 1 + print "failed to update data for '%s': %s" % (self.location, e) + else: + # set the last update timestamp + self.last_update = os.path.getmtime(self.cacheCurrent) + + # now convert the self.weatherData structure to parsable information + self.convertWeatherData() + try: + self.convertWeatherData() + except Exception, error: + print 'Failed to convert data for %s: %s' % (self.location, error) + + + def needsRefresh(self): + '''is the cache too old?''' + _debug_('needsRefresh()', 2) + if (os.path.isfile(self.cacheCurrent) == 0 or \ + (abs(time.time() - os.path.getmtime(self.cacheCurrent)) > WEATHER_AGE)): + return 1 + else: + return 0 + + def updateData(self): + '''update the cache data from the 1click service + @notes the elocation is not updated as it is static + ''' + _debug_('updateData()', 2) + if GUI: + popup = PopupBox(text=_('Fetching Weather for %s...') % self.popupParam) + popup.show() + + if not os.path.isfile(self.cacheElocation): + try: + elocationData = wget(self.url_eloc) + self.elocationData = elocationData + except Exception, error: + print 'Failed to get extended location data for %s: %s' % (self.location, error) + else: + self.elocationData = util.read_pickle(self.cacheElocation) + + try: + self.currentData = wget(self.url_curc) + #print 'currentData:', self.currentData + except Exception, error: + print 'Failed to get the current conditions data for %s: %s' % (self.location, error) + if os.path.isfile(self.cacheCurrent): + self.currentData = util.read_pickle(self.cacheCurrent) + else: + self.currentData = None + try: + self.forecastData = wget(self.url_dayf) + #print 'forecastData:', self.forecastData + except Exception, error: + print 'Failed to get the forecast data for %s: %s' % (self.location, error) + if os.path.isfile(self.cacheForecast): + self.forecastData = util.read_pickle(self.cacheForecast) + else: + self.forecastData = None + + if GUI: + popup.destroy() + + if not self.currentData or not self.forecastData: + # raise an error + return + + self.saveToCache() + return + + + def updateMap(self): + ''' Update the weather map ''' + _debug_('updateMap()', 2) + # obtain radar map + if self.mapuri: + try: + if GUI: + popup = PopupBox(text=_('Fetching Radar Map for %s...') % self.popupParam) + popup.show() + try: + self.weatherMapData = wget(self.mapuri) + self.saveMapToCache() + except Exception, error: + print 'Cannot download the map for "%s" from %s: %s' % (self.location, self.mapuri, error) + return + finally: + if GUI: + popup.destroy() + + try: + if GUI: + popup = PopupBox(text=_('Fetching Radar Map for %s...') % self.popupParam) + popup.show() + # get the first web page + weatherPage = wget(self.mapurl) + if config.DEBUG: + f = open(self.mapPage1, 'w') + f.write(weatherPage) + f.close() + try: + # find link to map page + regexp = re.compile ('if \(isMinNS4\) var mapNURL = "([^"]*)";', re.IGNORECASE) + results = regexp.search(weatherPage) + print 'weatherPage=%r' % (results.groups()) + weatherPage2 = "http://www.weather.com/%s" % (results.groups()[0]) + + mapPage = wget(weatherPage2) + if config.DEBUG: + f = open(self.mapPage2, 'w') + f.write(weatherPage) + f.close() + # find a link to the real weather map + regexp = re.compile('<img NAME="mapImg" SRC="(http://image.weather.com[^"]*jpg)"', re.IGNORECASE) + results = regexp.search(mapPage) + print 'mapPage=%r' % (results.groups()) + self.mapuri = results.groups()[0] + self.weatherMapData = wget(self.mapuri) + self.saveMapToCache() + return + except Exception, error: + print 'Cannot download the map for "%s" from %s: %s' % (self.location, self.mapurl, error) + + finally: + if GUI: + popup.destroy() + + def saveToCache(self): + _debug_('saveToCache()', 2) + util.save_pickle(self.elocationData, self.cacheElocation) + util.save_pickle(self.currentData, self.cacheCurrent) + util.save_pickle(self.forecastData, self.cacheForecast) + + def saveMapToCache(self): + ''' save weather map to the cache ''' + try: + if self.weatherMapData is not None: + imgfd = os.open(self.mapFile, os.O_CREAT|os.W_OK) + os.write(imgfd, self.weatherMapData) + os.close(imgfd) + except Exception, error: + print 'failed saving weather map to cache "%s": %s' % (self.mapFile, error) + + def loadFromCache(self): + ''' load the data and the map from the cache ''' + _debug_('loadFromCache()', 2) + self.elocationData = util.read_pickle(self.cacheElocation) + self.currentData = util.read_pickle(self.cacheCurrent) + self.forecastData = util.read_pickle(self.cacheForecast) + + try: + size = int(os.stat(self.mapFile)[6]) + except Exception, error: + _debug_('failed loading weather map for "%s" from cache: %s' % (self.location, error), config.DWARNING) + pass + else: + imgfd = os.open(self.mapFile, os.R_OK) + self.weatherMapData = os.read(imgfd, size) + os.close(imgfd) + + def actions(self): + ''' return a list of actions for this item ''' + _debug_('actions()', 2) + return [ (self.start_detailed_interface, _('Show Weather Details')) ] + + def start_detailed_interface(self, arg=None, menuw=None): + ''' detail handler ''' + _debug_('start_detailed_interface(arg=%r, menuw=%r)' % (arg, menuw), 2) + WeatherDetailHandler(arg, menuw, self) + + def isValid(self): + ''' reports is an error was detected ''' + _debug_('isValid()', 2) + return not self.error + + def getLastUpdated(self): + ''' parse the lsup time + @notes there seems to be a problem with AM/PM not parsing correctly + ''' + _debug_('getLastUpdated() "%s"' % self.updated, 2) + try: + value = time.strptime(self.updated.replace(' AM Local Time', ''), '%m/%d/%y %H:%M') + return time.strftime("%c", time.localtime(time.mktime(value))) + except ValueError, e: + try: + value = time.strptime(self.updated.replace(' PM Local Time', ''), '%m/%d/%y %H:%M') + (year, mon, day, hour, min, sec, weekday, yearday, saving) = value + value = (year, mon, day, hour+12, min, sec, weekday, yearday, saving) + return time.strftime("%c", time.localtime(time.mktime(value))) + except ValueError, error: + print error + return self.updated.replace(' Local Time', '') + #if self.ismetric: + # return time.strftime("%d/%m/%Y %H:%M:%S", time.localtime(self.updated)) + #else: + # return time.strftime("%m/%d/%Y %I:%M:%S %p", time.localtime(self.updated)) + + def getObservationStation(self): + ''' get the observation station ''' + _debug_('getObservationStation()', 2) + return "%s" % (self.observation_station) + + def getTemperature(self): + _debug_('getTemperature()', 2) + return u"%s\xb0%s" % (self.temperature, self.unit_t) + + def getFeeling(self): + _debug_('getFeeling()', 2) + return u"%s\xb0%s" % (self.feeling, self.unit_t) + + def getCurrentCondition(self): + ''' gets the current conditions ''' + _debug_('getCurrentCondition()', 2) + return "%s" % (self.current_conditions) + + def getIcon(self): + ''' gets the current conditions icon ''' + _debug_('getIcon()', 2) + return "%s" % (self.icon) + + def getPressure(self): + _debug_('getPressure()', 2) + if self.pressure_change == 'N/A': + return "%s %s" % (self.pressure, self.unit_p) + return "%s %s %s %s" % (self.pressure, self.unit_p, _('and'), self.pressure_change) + + def getPressureValue(self): + _debug_('getPressureValue()', 2) + return "%s %s" % (self.pressure, self.unit_p) + + def getPressureChange(self): + _debug_('getPressureChange()', 2) + return "%s" % (self.pressure_change) + + def getWind(self): + _debug_('getWind()', 2) + if self.wind_direction == 'CALM': + return "%s" % (self.wind_speed) + elif self.wind_direction == 'VAR': + return "%s %s %s %s" % (_('Variable'), _('at'), self.wind_speed, self.unit_s) + return "%s %s %s %s" % (self.wind_direction, _('at'), self.wind_speed, self.unit_s) + + def getWindDir(self): + _debug_('getWindDir()', 2) + return "%s" % (self.wind_direction) + + def getWindSpeed(self): + _debug_('getWindSpeed()', 2) + return "%s %s" % (self.wind_speed, self.unit_s) + + def getHumidity(self): + _debug_('getHumidity()', 2) + return "%s%%" % (self.humidity) + + def getVisibility(self): + _debug_('getVisibility()', 2) + return "%s %s" % (self.visibility, self.unit_d) + + def getUvIndex(self): + _debug_('getUvIndex()', 2) + return "%s" % (self.uv_index) + + def getUvType(self): + _debug_('getUvType()', 2) + return "%s" % (self.uv_type) + + def getDewPoint(self): + _debug_('getDewPoint()', 2) + return u"%s\xb0%s" % (self.dew_point, self.unit_t) + + def getMoonIcon(self): + _debug_('getMoonIcon()', 2) + return "%s" % (self.moon_icon) + + def getMoonPhase(self): + _debug_('getMoonPhase()', 2) + return "%s" % (self.moon_phase) + + def getSunrise(self): + _debug_('getSunrise()', 2) + return "%s" % (self.sunrise) + + def getSunset(self): + _debug_('getSunset()', 2) + return "%s" % (self.sunset) + + + def convertWeatherData(self): + ''' + convert the xml weather information for the skin + ''' + _debug_('convertWeatherData()', 2) + #print self.elocationData + #print self.currentData + #print self.forecastData + elocation = WeatherData(ET.XML(self.elocationData)) + current = WeatherData(ET.XML(self.currentData)) + forecast = WeatherData(ET.XML(self.forecastData)) + + if not self.name: + self.name = elocation.loc.dnam + + dnam = elocation.loc.dnam.split(', ') + ctry = elocation.eloc.ctry + if ctry in ('US'): + self.city = dnam[0] + self.state = dnam[1] + self.country = ctry + else: + self.city = dnam[0] + self.state = '' + self.country = dnam[1] + print 'city=%s, state=%s, country=%s' % (self.city, self.state, self.country) + + # reset variables + self.date = [] + self.weatherIcon = [] + self.highTemp = [] + self.lowTemp = [] + self.weatherType = [] + + self.unit_t = current.head.ut + self.unit_d = current.head.ud + self.unit_s = current.head.us + self.unit_p = current.head.up + self.unit_r = current.head.ur + + self.tm = current.loc.tm + self.latitude = current.loc.lat + self.longitude = current.loc.lon + self.sunrise = current.loc.sunr + self.sunset = current.loc.suns + self.zone = current.loc.zone + + self.updated = current.cc.lsup + self.observation_station = current.cc.obst + self.temperature = current.cc.tmp + self.feeling = current.cc.flik + self.current_conditions = current.cc.t + self.icon = current.cc.icon + self.pressure = current.cc.bar.r + self.pressure_change = current.cc.bar.d + self.wind_speed = current.cc.wind.s + self.wind_direction = current.cc.wind.t + self.humidity = current.cc.hmid + self.visibility = current.cc.vis + self.uv_index = current.cc.uv.i + self.uv_type = current.cc.uv.t + self.dew_point = current.cc.dewp + self.moon_icon = current.cc.moon.icon + self.moon_phase = current.cc.moon.t + + self.description = '%s %s %s' % (self.current_conditions, _("at"), self.getTemperature()) + self.image = self.getDayImage(self.icon) + + # skip today in the days + for day in forecast.dayf.days[1:]: + self.date.append(day.t) + self.highTemp.append(day.hi) + self.lowTemp.append(day.low) + for part in day.parts: + if part.p == 'd': + self.weatherIcon.append(self.getDayImage(part.icon)) + self.weatherType.append(part.t) + + + def getDayImage(self, num): + '''obtain the weather icons for multiple day forecast''' + _debug_('getDayImage()', 2) + + icon = os.path.join(WEATHER_DIR, WEATHER_ICONS[num][1]) + if not os.path.isfile(icon): + icon = os.path.join(WEATHER_DIR, WEATHER_ICONS[num][2]) + if not os.path.isfile(icon): + icon = os.path.join(WEATHER_DIR, WEATHER_ICONS[num][0]) + if not os.path.isfile(icon): + icon = os.path.join(WEATHER_DIR, 'na.png') + print '%s: %s %s' % (num, icon, os.path.split(WEATHER_ICONS[num][1])[1]) + return icon + + + def getMoonImage(self, num): + '''obtain the weather icons for multiple day forecast''' + _debug_('getMoonImage()', 2) + + icon = os.path.join(WEATHER_DIR, 'moons', '%s.png' % (num)) + print '%s: %s' % (num, icon) + return icon + + +class WeatherMainMenu(Item): + ''' + this is the item for the main menu and creates the list + of Weather Locations in a submenu. + ''' + def __init__(self, parent): + _debug_('WeatherMainMenu.__init__(parent=%r)' % (parent), 2) + Item.__init__(self, parent, skin_type='oneclick') + self.parent = parent + self.name = _('Weather') + + def actions(self): + ''' return a list of actions for this item ''' + _debug_('actions()', 2) + items = [ (self.create_locations_menu , _('Locations')) ] + return items + + def __call__(self, arg=None, menuw=None): + ''' call first action in the actions list ''' + _debug_('__call__(arg=%r, menuw=%r)' % (arg, menuw), 2) + if self.actions(): + return self.actions()[0][0](arg=arg, menuw=menuw) + + def create_locations_menu(self, arg=None, menuw=None): + ''' ''' + _debug_('create_locations_menu(arg=%r, menuw=%r)' % (arg, menuw), 2) + locations = [] + autoselect = 0 + # create menu items + for location in config.ONECLICK_LOCATIONS: + weather_item = WeatherItem(self, location) + # Only display this entry if no errors were found + if weather_item.isValid(): + locations.append (weather_item) + + # if only 1 valid location, autoselect it and go right to the detail screen + if locations.__len__() == 1: + autoselect = 1 + menuw.hide(clear=False) + + # if no locations were found, add a menu entry indicating that + if not locations: + nolocation = menu.MenuItem(_('No locations specified'), menuw.goto_prev_page, 0) + locations.append(nolocation) + + # if only 1 valid menu entry present, autoselect it + if autoselect: + locations[0](menuw=menuw) + else: + # create menu + weather_site_menu = menu.Menu(_('Locations'), locations) + menuw.pushmenu(weather_site_menu) + menuw.refresh() + +class WeatherDetailHandler: + ''' + A handler class to display several detailed forecast screens and catch events + ''' + def __init__(self, arg=None, menu=None, weather=None): + ''' ''' + _debug_('WeatherDetailHandler.__init__(arg=%r, menu=%r, weather=%r)' % (arg, menu, weather), 2) + self.arg = arg + self.menuw = menu + self.weather = weather + self.menuw.hide(clear=False) + rc.app(self) + + self.skins = ('day', 'forecast', 'week', 'map') + + self.skin_num = 0 + self.subtitles = (_('Current Conditions'), _('Today\'s Forecast'), + _('Extended Forecast'), _('Radar Map')) + + self.title = '' + self.subtitle = self.getSubtitle(self.skin_num) + + # Fire up splashscreen and load the plugins + skin.draw('oneclick', self) + + def prevSkin(self): + '''decrements the skin number round to the last skin''' + _debug_('prevSkin()', 2) + self.skin_num -= 1 + + # out of bounds check, reset to size of skins array + if self.skin_num < 0: + self.skin_num = len(self.skins)-1 + self.subtitle = self.getSubtitle(self.skin_num) + + def nextSkin(self): + '''increment the skin number round to the first skin''' + _debug_('nextSkin()', 2) + self.skin_num += 1 + + # out of bounds check, reset to 0 + if self.skin_num >= len(self.skins): + self.skin_num = 0 + self.subtitle = self.getSubtitle(self.skin_num) + + def getSubtitle(self, num): + ''' returns the subtitle for a skin number ''' + _debug_('getSubtitle(num=%s)' % (num), 2) + return '%s %s %s' % (self.subtitles[num], _('for'), self.weather.name) + + def eventhandler(self, event, menuw=None): + '''eventhandler''' + _debug_('eventhandler(event=%s, menuw=%r)' % (event, menuw), 2) + if event == 'MENU_BACK_ONE_MENU': + rc.app(None) + self.menuw.show() + return True + + elif event == 'MENU_SELECT': + self.weather.getForecast(force=1) + skin.clear() + skin.draw('oneclick', self) + return True + + elif event in ('MENU_DOWN', 'MENU_RIGHT'): + self.nextSkin() + skin.draw('oneclick', self) + return True + + elif event in ('MENU_UP', 'MENU_LEFT'): + self.prevSkin() + skin.draw('oneclick', self) + return True + + return False + +if __name__ == '__main__': + for location in config.ONECLICK_LOCATIONS: + print location + weather_item = WeatherItem(None, location) + print weather_item + import sys + sys.exit(1) + +class WeatherBaseScreen(skin.Area): + ''' A base class for weather screens to inherit from, provides common members+methods ''' + def __init__(self): + _debug_('WeatherBaseScreen.__init__()', 2) + skin.Area.__init__(self, 'content') + + # Weather display fonts + self.key_font = skin.get_font('medium0') + self.val_font = skin.get_font('medium1') + self.small_font = skin.get_font('small0') + self.big_font = skin.get_font('huge0') + + # set the multiplier to be used in all screen drawing + self.xmult = float(osd.width - (config.OSD_OVERSCAN_LEFT+config.OSD_OVERSCAN_RIGHT)) / 800 + self.ymult = float(osd.height - (config.OSD_OVERSCAN_TOP+config.OSD_OVERSCAN_BOTTOM)) / 600 + + self.update_functions = (self.update_day, self.update_forecast, + self.update_week, self.update_map) + + def update_day(self): + ''' + ''' + _debug_('update_day()', 2) + # display data + text = _('Humidity') + value = self.parent.weather.getHumidity() + + x_col1 = self.content.x + (50 * self.xmult) + x_col2 = self.content.x + (250 * self.xmult) + y_start = self.content.y + (60 * self.xmult) + y_inc = 40 * self.ymult + + self.write_text(text, self.key_font, self.content, + x=x_col1, y=y_start, height=-1, align_h='left') + self.write_text(value, self.val_font, self.content, + x=x_col2, y=y_start, height=-1, align_h='left') + + text = _('Pressure') + value = self.parent.weather.getPressure() + self.write_text(text, self.key_font, self.content, + x=x_col1, y=y_start+y_inc, height=-1, align_h='left') + self.write_text(value, self.val_font, self.content, + x=x_col2, y=y_start+y_inc, height=-1, align_h='left') + + text = _('Wind') + value = '%s' % (self.parent.weather.getWind()) + y_start += y_inc + self.write_text(text, self.key_font, self.content, + x=x_col1, y=y_start+y_inc, height=-1, align_h='left') + self.write_text(value, self.val_font, self.content, + x=x_col2, y=y_start+y_inc, height=-1, align_h='left') + + text = _('Wind Chill') + value = self.parent.weather.getFeeling() + y_start += y_inc + self.write_text(text, self.key_font, self.content, + x=x_col1, y=y_start+y_inc, height=-1, align_h='left') + self.write_text(value, self.val_font, self.content, + x=x_col2, y=y_start+y_inc, height=-1, align_h='left') + + text = _('Visibility') + value = self.parent.weather.getVisibility() + y_start += y_inc + self.write_text(text, self.key_font, self.content, + x=x_col1, y=y_start+y_inc, height=-1, align_h='left') + self.write_text(value, self.val_font, self.content, + x=x_col2, y=y_start+y_inc, height=-1, align_h='left') + + text = _('UV Index') + value = self.parent.weather.getUvType() + y_start += y_inc + self.write_text(text, self.key_font, self.content, + x=x_col1, y=y_start+y_inc, height=-1, align_h='left') + self.write_text(value, self.val_font, self.content, + x=x_col2, y=y_start+y_inc, height=-1, align_h='left') + + # draw current condition image + x_start = self.content.x + (450*self.xmult) + y_start = self.content.y + (40*self.ymult) + self.draw_image(self.parent.weather.image, + (x_start, y_start, + int(200*self.xmult), int(150*self.ymult))) + + y_start = self.content.y + (200*self.ymult) + self.write_text(self.parent.weather.getCurrentCondition(), + self.key_font, self.content, + x=x_start, y=y_start, + width=200*self.xmult, height=-1, align_h='center') + y_start = self.content.y + (250*self.ymult) + self.write_text(self.parent.weather.getTemperature(), + self.big_font, self.content, + x=x_start, y=y_start, + width=200*self.xmult, height=-1, align_h='center') + + x_start = self.content.x + (40*self.xmult) + y_start = self.content.y + (350*self.ymult) + self.write_text(self.parent.weather.getLastUpdated() , + self.small_font, self.content, + x=x_start, y=y_start, + width=self.content.width, height=-1, align_h='left') + + + def update_forecast(self): + ''' this screen is taken from the 1click weather forecast of the firefox plug-in. ''' + _debug_('update_forecast()', 2) + x_start = self.content.x + (20 * self.xmult) + y_start = self.content.y + (30 * self.xmult) + weather = self.parent.weather + + lines = [] + try: + lines.append('%s %s %s %s' % + (_('As of:'), weather.getLastUpdated(), _('in'), weather.getObservationStation())) + lines.append(' %s' % (weather.getCurrentCondition())) + lines.append(' %s %s' % (_('Temperature:'), weather.getTemperature())) + lines.append(' %s %s' % (_('Dew Point:'), weather.getDewPoint())) + lines.append(' %s %s' % (_('Humidity:'), weather.getHumidity())) + lines.append(' %s %s' % (_('Visibility:'), weather.getVisibility())) + lines.append(' %s %s' % (_('Pressure:'), weather.getPressure())) + lines.append(' %s %s' % (_('Winds:'), weather.getWind())) + lines.append('%s' % (_('Tonight:'))) + lines.append(' %s %s' % (_('Sunset:'), weather.getSunset())) + lines.append(' %s %s' % (_('Moon Phase:'), weather.getMoonPhase())) + except Exception, error: + print error + import traceback, sys + output = apply(traceback.format_exception, sys.exc_info()) + output = ''.join(output) + output = urllib.unquote(output) + print output + + y = y_start + for line in lines: + self.write_text(line, self.key_font, self.content, + x=x_start, y=y, height=-1, align_h='left') + y += (30 * self.ymult) + + try: + x_start = self.content.x + (500 * self.xmult) + y_start = self.content.y + (300 * self.ymult) + iconFile = weather.getMoonImage(weather.getMoonIcon()) + #self.draw_image(iconFile, + # (x_start, y_start, 90, 90)) + #(x_start, y_start, int(60 * self.xmult), int(60 * self.ymult))) + print 'x_start=%s, y_start=%s, xmult=%s, ymult=%s' % (x_start, y_start, self.xmult, self.ymult) + except Exception, error: + print error + + + def update_week(self): + ''' update the weeks forecast + @remarks this can be improved + ''' + _debug_('update_week()', 2) + + x_start = self.content.x + (10 * self.xmult) + y_start = self.content.y + (10 * self.xmult) + + day = 0 + for x in (0, 180, 360, 540): + + x2_start = x_start + (x *self.xmult) + y2_start = y_start + + self.write_text(Unicode(self.parent.weather.date[day]), + self.key_font, self.content, + x=x2_start, y=y2_start, + width=150*self.xmult, height=-1, align_h='center') + + iconFile = os.path.join(WEATHER_DIR, self.parent.weather.weatherIcon[day]) + self.draw_image(iconFile, + (x2_start, y2_start + (50*self.ymult), int(160*self.xmult), int(120*self.ymult))) + self.write_text(self.parent.weather.weatherType[day], + self.small_font, self.content, + x=x2_start, y=y2_start + (200*self.ymult), + width=160*self.xmult, height=-1, align_h='center') + self.write_text(_("LO"), + self.val_font, self.content, + x=x2_start, y=y2_start + (260*self.ymult), + width=90*self.xmult, height=-1, align_h='center') + self.write_text(self.parent.weather.lowTemp[day], + self.key_font, self.content, + x=x2_start, y=y2_start + (300*self.ymult), + width=90*self.xmult, height=-1, align_h='center') + self.write_text(_("HI"), + self.val_font, self.content, + x=x2_start+(70*self.xmult), y=y2_start + (260*self.ymult), + width=90*self.xmult, height=-1, align_h='center') + self.write_text(self.parent.weather.highTemp[day], + self.key_font, self.content, + x=x2_start+(70*self.xmult), y=y2_start + (300*self.ymult), + width=90*self.xmult, height=-1, align_h='center') + day += 1 + + def update_map(self): + ''' update the contents of the skin's doppler weather map ''' + _debug_('update_map()', 2) + if not self.parent.weather.weatherMapData: + x_start = self.content.x + (10 * self.xmult) + y_start = self.content.y + (10 * self.xmult) + self.write_text(_("Error encountered while trying to download weather map"), + self.key_font, self.content, x=x_start, y=y_start, + width=self.content.width, height=-1, align_h='left') + else: + self.draw_image(self.parent.weather.mapFile, + (self.content.x-3, self.content.y+14, self.content.width, self.content.height)) + + def update_content(self): + ''' update the contents of the skin ''' + _debug_('update_content()', 2) + self.parent = self.menu + self.content = self.calc_geometry(self.layout.content, copy_object=True) + self.update_functions[self.menu.skin_num]() + + +# create one instance of the WeatherType class +skin.register ('oneclick', ('screen', 'subtitle', 'title', 'plugin', WeatherBaseScreen())) Added: branches/rel-1/freevo/src/plugins/weatherdata.py ============================================================================== --- (empty file) +++ branches/rel-1/freevo/src/plugins/weatherdata.py Tue Sep 25 12:58:53 2007 @@ -0,0 +1,460 @@ +#!/usr/bin/env python +# +# using Firefox 1click weather +# wget 'http://ff.1click.weather.com/weather/local/SZXX0033?dayf=5&unit=m' +# wget 'http://ff.1click.weather.com/weather/local/SZXX0033?cc=*&unit=m' + +import cElementTree as ET +import cPickle, pickle +from pprint import pprint + + +class WeatherData: + """Main weather data class + """ + def __init__(self, tree): + self.tree = tree + self.head = None + self.loc = None + self.eloc = None + self.cc = None + self.dayf = None + self.TREE = { + 'head' : 'WeatherData.Head', + 'loc' : 'WeatherData.Loc', + 'eloc' : 'WeatherData.Eloc', + 'cc' : 'WeatherData.Cc', + 'dayf' : 'WeatherData.DayF', + } + parse(self) + + def __str__(self): + return 'WeatherData' + + +#391 lines + class Loc: + """ Location information + <loc id="SZXX0033"> + <dnam>Zurich, Switzerland</dnam> + <tm>7:46 AM</tm> + <lat>47.38</lat> + <lon>8.54</lon> + <sunr>6:57 AM</sunr> + <suns>7:47 PM</suns> + <zone>2</zone> + </loc> + """ + def __init__(self, tree): + self.tree = tree + self.id = None + self.dnam = None + self.tm = None + self.lat = None + self.lon = None + self.sunr = None + self.suns = None + self.zone = None + self.TREE = { + 'dnam' : 'dnam', + 'tm' : 'tm', + 'lat' : 'lat', + 'lon' : 'lon', + 'sunr' : 'sunr', + 'suns' : 'suns', + 'zone' : 'zone', + } + parse(self) + + + def __str__(self): + return 'Loc' + + class Head: + """ Header information + <head> + <locale>en_US</locale> + <form>MEDIUM</form> + <ut>C</ut> + <ud>km</ud> + <us>km/h</us> + <up>mb</up> + <ur>mm</ur> + </head> + """ + + def __init__(self, tree): + self.tree = tree + self.locale = None + self.form = None + self.ut = None + self.ud = None + self.us = None + self.mb = None + self.mm = None + self.TREE = { + 'locale' : 'locale', + 'form' : 'form', + 'ut' : 'ut', + 'ud' : 'ud', + 'us' : 'us', + 'up' : 'up', + 'ur' : 'ur', + } + parse(self) + + def __str__(self): + return 'Head' + + + class Eloc: + """ Extended location + <eloc id="SZXX0033"> + <dma>N/A</dma> + <rgn4>N/A</rgn4> + <rgn9>N/A</rgn9> + <st>*</st> + <ctry>SZ</ctry> + <zip>N/A</zip> + </eloc> + """ + def __init__(self, tree): + self.tree = tree + self.id = None + self.dma = None + self.rgn4 = None + self.rgn9 = None + self.st = None + self.ctry = None + self.zip = None + self.TREE = { + 'id' : 'id', + 'dma' : 'dma', + 'rgn4' : 'rgn4', + 'rgn9' : 'rgn9', + 'st' : 'st', + 'ctry' : 'ctry', + 'zip' : 'zip', + } + parse(self) + + def __str__(self): + return 'Eloc' + + class Cc: + """ Current conditions + <cc> + <lsup>9/11/07 7:20 AM Local Time</lsup> + <obst>Zurich, Switzerland</obst> + <tmp>9</tmp> + <flik>9</flik> + <t>Partly Cloudy</t> + <icon>30</icon> + <bar> + </bar> + <wind> + </wind> + <hmid>93</hmid> + <vis>10.0</vis> + <uv> + </uv> + <dewp>8</dewp> + <moon> + </moon> + </cc> + """ + def __init__(self, tree): + self.tree = tree + self.lsup = None + self.obst = None + self.tmp = None + self.flik = None + self.t = None + self.icon = None + self.bar = None + self.wind = None + self.hmid = None + self.vis = None + self.uv = None + self.dewp = None + self.moon = None + self.TREE = { + 'lsup' : 'lsup', + 'obst' : 'obst', + 'tmp' : 'tmp', + 'flik' : 'flik', + 't' : 't', + 'icon' : 'icon', + 'bar' : 'WeatherData.Bar', + 'wind' : 'WeatherData.Wind', + 'hmid' : 'hmid', + 'vis' : 'vis', + 'uv' : 'WeatherData.Uv', + 'dewp' : 'dewp', + 'moon' : 'WeatherData.Moon', + } + parse(self) + + def __str__(self): + return 'Cc' + + + class Bar: + """ + <bar> + <r>1020.0</r> + <d>rising</d> + </bar> + """ + def __init__(self, tree): + self.tree = tree + self.r = None + self.d = None + self.TREE = { + 'r' : 'r', + 'd' : 'd', + } + parse(self) + + def __str__(self): + return 'Bar' + + + class Uv: + """ + <uv> + <i>0</i> + <t>Low</t> + </uv> + """ + def __init__(self, tree): + self.tree = tree + self.i = None + self.t = None + self.TREE = { + 'i' : 'i', + 't' : 't', + } + parse(self) + + def __str__(self): + return 'Uv' + + + class Moon: + """ + <moon> + <icon>29</icon> + <t>New</t> + </moon> + """ + def __init__(self, tree): + self.tree = tree + self.icon = None + self.t = None + self.TREE = { + 'icon' : 'icon', + 't' : 't', + } + parse(self) + + def __str__(self): + return 'Moon' + + + class DayF: + """ Day forecast + <dayf> + <lsup>9/11/07 2:27 AM Local Time</lsup> + <day d="0" t="Tuesday" dt="Sep 11"> + </day> + </dayf> + """ + def __init__(self, tree): + self.tree = tree + self.lsup = None + self.day = None + self.TREE = { + 'lsup' : 'lsup', + 'day' : 'WeatherData.Day', + } + parse(self) + + def __str__(self): + return 'DayF' + + class Day: + """ Day information + <day d="4" t="Saturday" dt="Sep 15"> + <hi>19</hi> + <low>12</low> + <sunr>7:03 AM</sunr> + <suns>7:39 PM</suns> + <part p="d"> + </part> + </day> + """ + def __init__(self, tree): + self.tree = tree + self.d = None + self.t = None + self.dt = None + self.hi = None + self.low = None + self.sunr = None + self.suns = None + self.parts = None + self.TREE = { + 'd' : 'd', + 't' : 't', + 'dt' : 'dt', + 'hi' : 'hi', + 'low' : 'low', + 'sunr' : 'sunr', + 'suns' : 'suns', + 'part' : 'WeatherData.Part', + } + parse(self) + + def __str__(self): + return 'Day' + + class Part: + """ Part of a day information + <part p="n"> + <icon>29</icon> + <t>Partly Cloudy</t> + <wind> + </wind> + <bt>P Cloudy</bt> + <ppcp>10</ppcp> + <hmid>82</hmid> + </part> + """ + def __init__(self, tree): + self.tree = tree + self.p = None + self.icon = None + self.t = None + self.wind = None + self.bt = None + self.ppcp = None + self.hmid = None + self.TREE = { + 'icon' : 'icon', + 't' : 't', + 'wind' : 'WeatherData.Wind', + 'bt' : 'bt', + 'ppcp' : 'ppcp', + 'hmid' : 'hmid', + } + parse(self) + + def __str__(self): + return 'Part' + + class Wind: + """ Wind information + <wind> + <s>6</s> + <gust>N/A</gust> + <d>109</d> + <t>ESE</t> + </wind> + """ + def __init__(self, tree): + self.tree = tree + self.s = None + self.gust = None + self.d = None + self.t = None + self.TREE = { + 's' : 's', + 'gust' : 'gust', + 'd' : 'd', + 't' : 't', + } + parse(self) + + def __str__(self): + return 'Wind' + + +def parse(obj): + """ Parse an object using the tree information and build a class hierarchy. + For list items add to a list of the name with a 's' appended + + This code is a little bit complex :) + """ + if hasattr(obj.tree, 'items'): + for k,v in obj.tree.items(): + code = 'obj.%s = "%s"' % (k, v) + exec code + + for k,v in obj.TREE.items(): + elements = obj.tree.findall(k) + if not elements: + continue + + # just one element + if len(elements) == 1: + for element in elements: + children = element.getchildren() + if children: + code = 'obj.%s = %s(element)' % (k, v) + else: + code = 'obj.%s = element.text.strip()' % (k) + exec code + continue + + # list of elements + code = 'obj.%ss = []' % k + exec code + for element in elements: + children = element.getchildren() + if children: + code = '%s = %s(element)' % (k, v) + else: + code = 'obj.%s = element.text.strip()' % (k) + exec code + code = 'obj.%ss.append(%s)' % (k, k) + exec code + continue + + return obj + + +if __name__ == '__main__': + location_tree=ET.parse('SZXX0033-eloc.xml') + location = WeatherData(location_tree) + conditions_tree=ET.parse('SZXX0033-cc.xml') + conditions = WeatherData(conditions_tree) + forecast_tree=ET.parse('SZXX0033-dayf5.xml') + forecast = WeatherData(forecast_tree) + print dir(forecast) + for i in dir(forecast): + item = eval('forecast.%s' % (i)) + #print i, type(item), item + #pprint(forecast) + f = open('forecast.pickle', 'w') + #pickle.dump(forecast, f, pickle.HIGHEST_PROTOCOL) + #pickle.dump(forecast_tree, f, pickle.HIGHEST_PROTOCOL) + f.close() + + print dir(forecast.loc) + print forecast.loc.id + print dir(forecast.dayf) + print forecast.dayf.lsup + print dir(forecast.dayf.day) + print type(forecast.dayf.days) + print forecast.dayf.days + for day in forecast.dayf.days: + print dir(day) + print day.dt + print type(day.parts) + for part in day.parts: + print dir(part) + print dir(part.wind) + print part.wind.s, part.wind.t + + print location.eloc.ctry |