2020-11-18 20:25:54 +00:00
import PySimpleGUI as sg
import datetime
import base64
from urllib import request
import json
import sys
2020-11-18 21:15:32 +00:00
import webbrowser
2020-11-18 20:25:54 +00:00
"""
A Current Weather Widget
Adapted from the weather widget originally created and published by Israel Dryer that you ' ll find here:
https : / / github . com / israel - dryer / Weather - App
BIG THANKS goes out for creating a good starting point for other widgets to be build from .
A true " Template " is being developed that is a little more abstracted to make creating your own
widgets easy . Things like the settings window is being standardized , the settings file format too .
You will need a key ( APPID ) from OpenWeathermap . org in order to run this widget . It ' s free, it ' s easy :
https : / / home . openweathermap . org /
Your initial location is determined using your IP address and will be used if no settings file is found
This widget is an early version of a PSG Widget so it may not share the same names / constructs as the templates .
Copyright 2020 PySimpleGUI - www . PySimpleGUI . com
"""
SETTINGS_PATH = ' . '
API_KEY = ' ' # Set using the "Settings" window and saved in your config file
sg . theme ( ' Light Green 6 ' )
ALPHA = 0.8
BG_COLOR = sg . theme_text_color ( )
TXT_COLOR = sg . theme_background_color ( )
APP_DATA = {
' City ' : ' New York ' ,
' Country ' : ' US ' ,
' Postal ' : 10001 ,
' Description ' : ' clear skys ' ,
' Temp ' : 101.0 ,
' Feels Like ' : 72.0 ,
' Wind ' : 0.0 ,
' Humidity ' : 0 ,
' Precip 1hr ' : 0.0 ,
' Pressure ' : 0 ,
' Updated ' : ' Not yet updated ' ,
' Icon ' : None ,
' Units ' : ' Imperial '
}
def load_settings ( ) :
global API_KEY
settings = sg . UserSettings ( path = SETTINGS_PATH )
API_KEY = settings [ ' -api key- ' ]
if not API_KEY :
sg . popup_quick_message ( ' No valid API key found... opening setup window... ' , keep_on_top = True , background_color = ' red ' , text_color = ' white ' ,
auto_close_duration = 3 , non_blocking = False )
change_settings ( settings )
return settings
def change_settings ( settings , window_location = ( None , None ) ) :
global APP_DATA , API_KEY
try :
nearest_postal = json . loads ( request . urlopen ( ' http://ipapi.co/json ' ) . read ( ) ) [ ' postal ' ]
except Exception as e :
print ( ' Error getting nearest postal ' , e )
nearest_postal = ' '
layout = [ [ sg . T ( ' Enter Zipcode or City for your location ' ) ] ,
[ sg . I ( settings . get ( ' -location- ' , nearest_postal ) , size = ( 15 , 1 ) , key = ' -LOCATION- ' ) ] ,
[ sg . I ( settings . get ( ' -api key- ' , ' ' ) , size = ( 32 , 1 ) , key = ' -API KEY- ' ) ] ,
2020-11-18 21:15:32 +00:00
[ sg . B ( ' Ok ' , border_width = 0 , bind_return_key = True ) , sg . B ( ' Register For a Key ' , border_width = 0 , k = ' -REGISTER- ' ) , sg . B ( ' Cancel ' , border_width = 0 ) ] , ]
2020-11-18 20:25:54 +00:00
window = sg . Window ( ' Settings ' , layout , location = window_location , no_titlebar = True , keep_on_top = True , border_depth = 0 )
event , values = window . read ( )
window . close ( )
2020-11-18 21:15:32 +00:00
if event == ' -REGISTER- ' :
sg . popup ( ' Launching browser so you can signup for the " Current Weather " service from OpenWeatherMap.org to get a Free API Key ' , ' Click OK and your browser will open ' , r ' Visit https://home.openweathermap.org/ for more information ' , location = window_location )
# Register to get a free key
webbrowser . open ( r ' https://home.openweathermap.org/users/sign_up ' )
2020-11-18 20:25:54 +00:00
if event == ' Ok ' :
user_location = settings [ ' -location- ' ] = values [ ' -LOCATION- ' ]
API_KEY = settings [ ' -api key- ' ] = values [ ' -API KEY- ' ]
else :
API_KEY = settings [ ' -api key- ' ]
user_location = settings [ ' -location- ' ]
if user_location is not None :
if user_location . isnumeric ( ) and len ( user_location ) == 5 and user_location is not None :
APP_DATA [ ' Postal ' ] = user_location
APP_DATA [ ' City ' ] = ' '
else :
APP_DATA [ ' City ' ] = user_location
APP_DATA [ ' Postal ' ] = ' '
return settings
def update_weather ( ) :
if APP_DATA [ ' City ' ] :
request_weather_data ( create_endpoint ( 2 ) )
elif APP_DATA [ ' Postal ' ] :
request_weather_data ( create_endpoint ( 1 ) )
def create_endpoint ( endpoint_type = 0 ) :
""" Create the api request endpoint
{ 0 : default , 1 : zipcode , 2 : city_name } """
if endpoint_type == 1 :
try :
endpoint = f " http://api.openweathermap.org/data/2.5/weather?zip= { APP_DATA [ ' Postal ' ] } ,us&appid= { API_KEY } &units= { APP_DATA [ ' Units ' ] } "
return endpoint
except ConnectionError :
return
elif endpoint_type == 2 :
try :
endpoint = f " http://api.openweathermap.org/data/2.5/weather?q= { APP_DATA [ ' City ' ] . replace ( ' ' , ' % 20 ' ) } ,us&APPID= { API_KEY } &units= { APP_DATA [ ' Units ' ] } "
return endpoint
except ConnectionError :
return
else :
return
def request_weather_data ( endpoint ) :
""" Send request for updated weather data """
global APP_DATA
if endpoint is None :
sg . popup_error ( ' Could not connect to api. endpoint is None ' , keep_on_top = True )
return
else :
try :
response = request . urlopen ( endpoint )
except request . HTTPError :
sg . popup_error ( ' ERROR Obtaining Weather Data ' ,
' Is your API Key set correctly? ' ,
API_KEY , keep_on_top = True )
return
if response . reason == ' OK ' :
weather = json . loads ( response . read ( ) )
APP_DATA [ ' City ' ] = weather [ ' name ' ] . title ( )
APP_DATA [ ' Description ' ] = weather [ ' weather ' ] [ 0 ] [ ' description ' ]
APP_DATA [ ' Temp ' ] = " {:,.0f} °F " . format ( weather [ ' main ' ] [ ' temp ' ] )
APP_DATA [ ' Humidity ' ] = " {:,d} % " . format ( weather [ ' main ' ] [ ' humidity ' ] )
APP_DATA [ ' Pressure ' ] = " {:,d} hPa " . format ( weather [ ' main ' ] [ ' pressure ' ] )
APP_DATA [ ' Feels Like ' ] = " {:,.0f} °F " . format ( weather [ ' main ' ] [ ' feels_like ' ] )
APP_DATA [ ' Wind ' ] = " {:,.1f} m/h " . format ( weather [ ' wind ' ] [ ' speed ' ] )
APP_DATA [ ' Precip 1hr ' ] = None if not weather . get ( ' rain ' ) else " {:2} mm " . format ( weather [ ' rain ' ] [ ' 1h ' ] )
APP_DATA [ ' Updated ' ] = ' Updated: ' + datetime . datetime . now ( ) . strftime ( " % B %d % I: % M: % S % p " )
APP_DATA [ ' Lon ' ] = weather [ ' coord ' ] [ ' lon ' ]
APP_DATA [ ' Lat ' ] = weather [ ' coord ' ] [ ' lat ' ]
icon_url = " http://openweathermap.org/img/wn/ {} @2x.png " . format ( weather [ ' weather ' ] [ 0 ] [ ' icon ' ] )
APP_DATA [ ' Icon ' ] = base64 . b64encode ( request . urlopen ( icon_url ) . read ( ) )
def metric_row ( metric ) :
""" Return a pair of labels for each metric """
return [ sg . Text ( metric , font = ( ' Arial ' , 10 ) , pad = ( 15 , 0 ) , size = ( 9 , 1 ) ) ,
sg . Text ( APP_DATA [ metric ] , font = ( ' Arial ' , 10 , ' bold ' ) , pad = ( 0 , 0 ) , size = ( 9 , 1 ) , key = metric ) ]
def create_window ( win_location ) :
""" Create the application window """
col1 = sg . Column (
[ [ sg . Text ( APP_DATA [ ' City ' ] , font = ( ' Arial Rounded MT Bold ' , 18 ) , pad = ( ( 10 , 0 ) , ( 50 , 0 ) ) , size = ( 18 , 1 ) , background_color = BG_COLOR , text_color = TXT_COLOR ,
key = ' City ' ) ] ,
[ sg . Text ( APP_DATA [ ' Description ' ] , font = ( ' Arial ' , 12 ) , pad = ( 10 , 0 ) , background_color = BG_COLOR , text_color = TXT_COLOR , key = ' Description ' ) ] ] ,
background_color = BG_COLOR , key = ' COL1 ' )
col2 = sg . Column (
[ [ sg . Text ( ' × ' , font = ( ' Arial Black ' , 16 ) , pad = ( 0 , 0 ) , justification = ' right ' , background_color = BG_COLOR , text_color = TXT_COLOR , enable_events = True ,
key = ' -QUIT- ' ) ] ,
[ sg . Image ( data = APP_DATA [ ' Icon ' ] , pad = ( ( 5 , 10 ) , ( 0 , 0 ) ) , size = ( 100 , 100 ) , background_color = BG_COLOR , key = ' Icon ' ) ] ] ,
element_justification = ' center ' , background_color = BG_COLOR , key = ' COL2 ' )
col3 = sg . Column (
[ [ sg . Text ( APP_DATA [ ' Updated ' ] , font = ( ' Arial ' , 8 ) , background_color = BG_COLOR , text_color = TXT_COLOR , key = ' Updated ' ) ] ] ,
pad = ( 10 , 5 ) , element_justification = ' left ' , background_color = BG_COLOR , key = ' COL3 ' )
col4 = sg . Column (
[ [ sg . Text ( ' Settings ' , font = ( ' Arial ' , 8 , ' italic ' ) , background_color = BG_COLOR , text_color = TXT_COLOR , enable_events = True , key = ' -CHANGE- ' ) ,
sg . Text ( ' Refresh ' , font = ( ' Arial ' , 8 , ' italic ' ) , background_color = BG_COLOR , text_color = TXT_COLOR , enable_events = True , key = ' -REFRESH- ' ) ] ] ,
pad = ( 10 , 5 ) , element_justification = ' right ' , background_color = BG_COLOR , key = ' COL4 ' )
top_col = sg . Column ( [ [ col1 , col2 ] ] , pad = ( 0 , 0 ) , background_color = BG_COLOR , key = ' TopCOL ' )
bot_col = sg . Column ( [ [ col3 , col4 ] ] , pad = ( 0 , 0 ) , background_color = BG_COLOR , key = ' BotCOL ' )
lf_col = sg . Column (
[ [ sg . Text ( APP_DATA [ ' Temp ' ] , font = ( ' Haettenschweiler ' , 90 ) , pad = ( ( 10 , 0 ) , ( 0 , 0 ) ) , justification = ' center ' , key = ' Temp ' ) ] ] ,
pad = ( 10 , 0 ) , element_justification = ' center ' , key = ' LfCOL ' )
rt_col = sg . Column (
[ metric_row ( ' Feels Like ' ) , metric_row ( ' Wind ' ) , metric_row ( ' Humidity ' ) , metric_row ( ' Precip 1hr ' ) , metric_row ( ' Pressure ' ) ] ,
pad = ( ( 15 , 0 ) , ( 25 , 5 ) ) , key = ' RtCOL ' )
layout = [ [ top_col ] ,
[ lf_col , rt_col ] ,
[ bot_col ] ]
window = sg . Window ( layout = layout , title = ' Weather Widget ' , margins = ( 0 , 0 ) , finalize = True , location = win_location ,
element_justification = ' center ' , keep_on_top = True , no_titlebar = True , grab_anywhere = True , alpha_channel = ALPHA )
for col in [ ' COL1 ' , ' COL2 ' , ' TopCOL ' , ' BotCOL ' , ' -QUIT- ' ] :
window [ col ] . expand ( expand_y = True , expand_x = True )
for col in [ ' COL3 ' , ' COL4 ' , ' LfCOL ' , ' RtCOL ' ] :
window [ col ] . expand ( expand_x = True )
window [ ' -CHANGE- ' ] . set_cursor ( ' hand2 ' )
window [ ' -QUIT- ' ] . set_cursor ( ' hand2 ' )
window [ ' -REFRESH- ' ] . set_cursor ( ' hand2 ' )
return window
def update_metrics ( window ) :
""" Adjust the GUI to reflect the current weather metrics """
metrics = [ ' City ' , ' Temp ' , ' Feels Like ' , ' Wind ' , ' Humidity ' , ' Precip 1hr ' ,
' Description ' , ' Icon ' , ' Pressure ' , ' Updated ' ]
for metric in metrics :
if metric == ' Icon ' :
window [ metric ] . update ( data = APP_DATA [ metric ] )
else :
window [ metric ] . update ( APP_DATA [ metric ] )
def main ( refresh_rate , win_location ) :
""" The main program routine """
refresh_in_milliseconds = refresh_rate * 60 * 1000
# Load settings from config file. If none found will create one
settings = load_settings ( )
location = settings [ ' -location- ' ]
if location is not None :
if location . isnumeric ( ) and len ( location ) == 5 and location is not None :
APP_DATA [ ' Postal ' ] = location
APP_DATA [ ' City ' ] = ' '
else :
APP_DATA [ ' City ' ] = location
APP_DATA [ ' Postal ' ] = ' '
update_weather ( )
else :
sg . popup_error ( ' Having trouble with location. Your location: ' , location )
exit ( )
window = create_window ( win_location )
while True : # Event Loop
event , values = window . read ( timeout = refresh_in_milliseconds )
if event in ( None , ' -QUIT- ' ) :
break
if event == ' -CHANGE- ' :
x , y = window . current_location ( )
settings = change_settings ( settings , ( x + 405 , y ) )
elif event == ' -REFRESH- ' :
sg . popup_quick_message ( ' Refreshing... ' , keep_on_top = True , background_color = ' red ' , text_color = ' white ' ,
auto_close_duration = 3 , non_blocking = False )
elif event != sg . TIMEOUT_KEY :
sg . Print ( ' Unknown event received \n Event & values: \n ' , event , values )
update_weather ( )
update_metrics ( window )
window . close ( )
if __name__ == ' __main__ ' :
if len ( sys . argv ) > 1 :
location = sys . argv [ 1 ] . split ( ' , ' )
location = ( int ( location [ 0 ] ) , int ( location [ 1 ] ) )
else :
location = ( None , None )
main ( refresh_rate = 1 , win_location = location )