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 .
2022-03-07 20:25:41 +00:00
Copyright 2020 , 2022 PySimpleGUI - www . PySimpleGUI . com
2020-11-18 20:25:54 +00:00
"""
2020-11-28 12:18:18 +00:00
SETTINGS_PATH = None # use the default settings path (OS settings foloder)
2020-11-18 20:25:54 +00:00
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 :
2020-11-28 12:18:18 +00:00
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 , location = win_location )
2020-11-18 20:25:54 +00:00
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- ' ) ] ,
2022-03-07 20:25:41 +00:00
[ sg . I ( settings . get ( ' -country- ' , ' US ' ) , size = ( 15 , 1 ) , key = ' -COUNTRY- ' ) ] ,
2020-11-18 20:25:54 +00:00
[ sg . I ( settings . get ( ' -api key- ' , ' ' ) , size = ( 32 , 1 ) , key = ' -API KEY- ' ) ] ,
2022-03-07 21:06:31 +00:00
[ sg . CBox ( ' Use Metric For Temperatures ' , default = settings . get ( ' -celsius- ' , False ) , key = ' -CELSIUS- ' ) ] ,
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- ' ]
2022-03-07 20:25:41 +00:00
settings [ ' -country- ' ] = values [ ' -COUNTRY- ' ]
2020-11-18 20:25:54 +00:00
API_KEY = settings [ ' -api key- ' ] = values [ ' -API KEY- ' ]
2022-03-07 21:06:31 +00:00
settings [ ' -celsius- ' ] = values [ ' -CELSIUS- ' ]
2020-11-18 20:25:54 +00:00
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 ' ] = ' '
2022-03-07 20:25:41 +00:00
APP_DATA [ ' Country ' ] = settings [ ' -country- ' ]
2022-03-07 21:06:31 +00:00
if settings [ ' -celsius- ' ] :
APP_DATA [ ' Units ' ] = ' metric '
else :
APP_DATA [ ' Units ' ] = ' imperial '
2020-11-18 20:25:54 +00:00
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 :
2022-03-07 20:25:41 +00:00
endpoint = f " http://api.openweathermap.org/data/2.5/weather?zip= { APP_DATA [ ' Postal ' ] } , { APP_DATA [ ' Country ' ] } &appid= { API_KEY } &units= { APP_DATA [ ' Units ' ] } "
2020-11-18 20:25:54 +00:00
return endpoint
except ConnectionError :
return
elif endpoint_type == 2 :
try :
2022-03-07 20:25:41 +00:00
# endpoint = f"http://api.openweathermap.org/data/2.5/weather?q={APP_DATA['City'].replace(' ', '%20')},us&APPID={API_KEY}&units={APP_DATA['Units']}"
endpoint = f " http://api.openweathermap.org/data/2.5/weather?q= { APP_DATA [ ' City ' ] . replace ( ' ' , ' % 20 ' ) } , { APP_DATA [ ' Country ' ] } &APPID= { API_KEY } &units= { APP_DATA [ ' Units ' ] } "
2020-11-18 20:25:54 +00:00
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 :
2020-11-28 12:18:18 +00:00
sg . popup_error ( ' Could not connect to api. endpoint is None ' , keep_on_top = True , location = win_location )
2020-11-18 20:25:54 +00:00
return
else :
try :
response = request . urlopen ( endpoint )
except request . HTTPError :
sg . popup_error ( ' ERROR Obtaining Weather Data ' ,
' Is your API Key set correctly? ' ,
2020-11-28 12:18:18 +00:00
API_KEY , keep_on_top = True , location = win_location )
2020-11-18 20:25:54 +00:00
return
2022-03-07 21:06:31 +00:00
if APP_DATA [ ' Units ' ] == ' metric ' :
temp_units , speed_units = ' °C ' , ' m/sec '
else :
temp_units , speed_units = ' °F ' , ' miles/hr '
2020-11-18 20:25:54 +00:00
if response . reason == ' OK ' :
weather = json . loads ( response . read ( ) )
APP_DATA [ ' City ' ] = weather [ ' name ' ] . title ( )
APP_DATA [ ' Description ' ] = weather [ ' weather ' ] [ 0 ] [ ' description ' ]
2022-03-07 21:06:31 +00:00
APP_DATA [ ' Temp ' ] = " {:,.0f} {} " . format ( weather [ ' main ' ] [ ' temp ' ] , temp_units )
2020-11-18 20:25:54 +00:00
APP_DATA [ ' Humidity ' ] = " {:,d} % " . format ( weather [ ' main ' ] [ ' humidity ' ] )
APP_DATA [ ' Pressure ' ] = " {:,d} hPa " . format ( weather [ ' main ' ] [ ' pressure ' ] )
2022-03-07 21:06:31 +00:00
APP_DATA [ ' Feels Like ' ] = " {:,.0f} {} " . format ( weather [ ' main ' ] [ ' feels_like ' ] , temp_units )
APP_DATA [ ' Wind ' ] = " {:,.1f} {} " . format ( weather [ ' wind ' ] [ ' speed ' ] , speed_units )
2020-11-18 20:25:54 +00:00
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 (
2020-11-28 12:18:18 +00:00
[ [ 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 ' ) ] ,
2020-11-18 20:25:54 +00:00
[ 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 (
2020-11-28 12:18:18 +00:00
[ [ sg . Text ( ' × ' , font = ( ' Arial Black ' , 16 ) , pad = ( 0 , 0 ) , justification = ' right ' , background_color = BG_COLOR , text_color = TXT_COLOR , enable_events = True , key = ' -QUIT- ' ) ] ,
2020-11-18 20:25:54 +00:00
[ 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 ] ,
2022-02-17 00:51:47 +00:00
[ bot_col ] ,
[ sg . Text ( f ' { sg . ver } { sg . framework_version } { sys . version } ' , font = ( ' Arial ' , 8 ) , background_color = BG_COLOR , text_color = TXT_COLOR , pad = ( 0 , 0 ) ) ] ]
2020-11-18 20:25:54 +00:00
window = sg . Window ( layout = layout , title = ' Weather Widget ' , margins = ( 0 , 0 ) , finalize = True , location = win_location ,
2020-11-28 12:18:18 +00:00
element_justification = ' center ' , keep_on_top = True , no_titlebar = True , grab_anywhere = True , alpha_channel = ALPHA ,
2022-02-17 00:51:47 +00:00
right_click_menu = [ [ ' ' ] , [ ' Edit Me ' , ' Versions ' , ' Exit ' , ] ] , enable_close_attempted_event = True )
2020-11-18 20:25:54 +00:00
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- ' ]
2022-03-07 20:25:41 +00:00
APP_DATA [ ' Country ' ] = settings . get ( ' -country- ' , ' US ' )
2022-03-07 21:06:31 +00:00
if settings . get ( ' -celsius- ' ) :
APP_DATA [ ' Units ' ] = ' metric '
else :
APP_DATA [ ' Units ' ] = ' imperial '
2020-11-18 20:25:54 +00:00
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 )
2022-02-17 00:51:47 +00:00
if event in ( None , ' -QUIT- ' , ' Exit ' , sg . WIN_CLOSE_ATTEMPTED_EVENT ) :
sg . user_settings_set_entry ( ' -win location- ' , window . current_location ( ) ) # The line of code to save the position before exiting
2020-11-18 20:25:54 +00:00
break
if event == ' -CHANGE- ' :
x , y = window . current_location ( )
2020-11-28 12:18:18 +00:00
settings = change_settings ( settings , ( x + 200 , y + 50 ) )
2020-11-18 20:25:54 +00:00
elif event == ' -REFRESH- ' :
sg . popup_quick_message ( ' Refreshing... ' , keep_on_top = True , background_color = ' red ' , text_color = ' white ' ,
2022-02-17 00:51:47 +00:00
auto_close_duration = 3 , non_blocking = False , location = ( window . current_location ( ) [ 0 ] + window . size [ 0 ] / / 2 - 30 , window . current_location ( ) [ 1 ] + window . size [ 1 ] / / 2 - 10 ) )
elif event == ' Edit Me ' :
sg . execute_editor ( __file__ )
elif event == ' Versions ' :
sg . main_get_debug_data ( )
2020-11-18 20:25:54 +00:00
elif event != sg . TIMEOUT_KEY :
2020-11-28 12:18:18 +00:00
sg . Print ( ' Unknown event received \n Event & values: \n ' , event , values , location = win_location )
2020-11-18 20:25:54 +00:00
update_weather ( )
update_metrics ( window )
window . close ( )
if __name__ == ' __main__ ' :
if len ( sys . argv ) > 1 :
2020-11-28 12:18:18 +00:00
win_location = sys . argv [ 1 ] . split ( ' , ' )
win_location = ( int ( win_location [ 0 ] ) , int ( win_location [ 1 ] ) )
2020-11-18 20:25:54 +00:00
else :
2022-02-17 00:51:47 +00:00
win_location = sg . user_settings_get_entry ( ' -win location- ' , ( None , None ) )
main ( refresh_rate = 1 , win_location = win_location )