1+ import tkinter as tk
2+ from tkinter import messagebox
3+ import requests
4+ import threading
5+ import time
6+ from dotenv import load_dotenv
7+ load_dotenv ()
8+ from geopy .geocoders import Nominatim
9+ import os
10+
11+ API_KEY = os .getenv ('OPENWEATHER_API_KEY' )
12+ if API_KEY is None :
13+ raise ValueError ("API key not found. Please set the OPENWEATHER_API_KEY in the .env file." )
14+
15+ def calculate_lat_long (city ):
16+ geolocator = Nominatim (user_agent = "weather_notifier" )
17+ location = geolocator .geocode (city )
18+ if location :
19+ return location .latitude , location .longitude
20+ return None ,None
21+ def get_weather (lat ,long ):
22+ try :
23+ print (lat ,long )
24+ url = f"https://api.openweathermap.org/data/2.5/weather?lat={ lat } &lon={ long } &units=metric&appid={ API_KEY } "
25+ res = requests .get (url )
26+ data = res .json ()
27+ if data ['cod' ] != 404 :
28+ weather_info = {
29+ "City" : data .get ("name" , "N/A" ),
30+ "Latitude" : data ['coord' ]['lat' ],
31+ "Longitude" : data ['coord' ]['lon' ],
32+ "Temperature" : data ['main' ]['temp' ],
33+ "Feels Like" : data ['main' ]['feels_like' ],
34+ "Min Temp" : data ['main' ]['temp_min' ],
35+ "Max Temp" : data ['main' ]['temp_max' ],
36+ "Pressure" : data ['main' ]['pressure' ],
37+ "Humidity" : data ['main' ]['humidity' ],
38+ "Wind Speed" : data ['wind' ]['speed' ],
39+ "Wind Degree" : data ['wind' ]['deg' ],
40+ "Weather" : data ['weather' ][0 ]['description' ].capitalize (),
41+ "Clouds" : f"{ data ['clouds' ]['all' ]} %" ,
42+ "Visibility" : data .get ('visibility' , "N/A" ),
43+ "Sunrise" : time .strftime ('%Y-%m-%d %H:%M:%S' , time .gmtime (data ['sys' ]['sunrise' ] + data ['timezone' ])),
44+ "Sunset" : time .strftime ('%Y-%m-%d %H:%M:%S' , time .gmtime (data ['sys' ]['sunset' ] + data ['timezone' ])),
45+ }
46+ return weather_info
47+ else :
48+ return None
49+
50+ except Exception as e :
51+ messagebox .showerror ("Error" , f"Error fetching weather data: { str (e )} " )
52+ return None
53+
54+ # updating the weather
55+ def update_weather ():
56+ city = city_entry .get ()
57+ if city :
58+ lat , lon = calculate_lat_long (city )
59+ if lat and lon :
60+ weather_info = get_weather (lat , lon )
61+ if weather_info :
62+ weather_info_in_str_to_display = covert_the_info_to_display (weather_info )
63+ weather_label .config (text = weather_info_in_str_to_display )
64+ stop_button .pack (pady = 5 )
65+ city_label .pack_forget ()
66+ city_entry .pack_forget ()
67+ manual_radio .pack_forget ()
68+ auto_radio .pack_forget ()
69+ start_button .pack_forget ()
70+ else :
71+ weather_label .config (text = "Unable to find coordinates!" )
72+ stop_button .pack_forget ()
73+ else :
74+ weather_label .config (text = "Unable to find coordinates!" )
75+ stop_button .pack_forget ()
76+ else :
77+ messagebox .showerror ("Error" , "Please enter a valid city name." )
78+ stop_button .pack_forget ()
79+ # displaying the info in the tkinter created box
80+ def covert_the_info_to_display (weather_info ):
81+ # Clear the previous text
82+ weather_info_in_str_to_display = f'''
83+ City: { weather_info ['City' ]} \n
84+ Coordinates: ({ weather_info ['Latitude' ]} , { weather_info ['Longitude' ]} )\n
85+ Temperature: { weather_info ['Temperature' ]} °C (Feels like { weather_info ['Feels Like' ]} °C)\n
86+ Min Temp: { weather_info ['Min Temp' ]} °C, Max Temp: { weather_info ['Max Temp' ]} °C\n
87+ Pressure: { weather_info ['Pressure' ]} hPa\n
88+ Humidity: { weather_info ['Humidity' ]} %\n
89+ Wind: { weather_info ['Wind Speed' ]} m/s, { weather_info ['Wind Degree' ]} °\n
90+ Clouds: { weather_info ['Clouds' ]} \n
91+ Visibility: { weather_info ['Visibility' ]} meters\n
92+ Weather: { weather_info ['Weather' ]} \n
93+ Sunrise: { weather_info ['Sunrise' ]} \n
94+ Sunset: { weather_info ['Sunset' ]} \n
95+ '''
96+ return weather_info_in_str_to_display
97+
98+ # run_in_background logic
99+ def run_in_background (interval ):
100+ while auto_mode .get ():
101+ update_weather ()
102+ time .sleep (interval )
103+
104+ # Function to handle click
105+ def start_notifier ():
106+ if auto_mode .get ():
107+ interval_str = interval_entry .get ().strip ()
108+ if not interval_str :
109+ messagebox .showerror ("Error" , "Please enter a valid interval (in seconds)." )
110+ return
111+ try :
112+ interval = int (interval_str )
113+ if interval <= 0 :
114+ messagebox .showerror ("Error" , "Please enter a valid interval (in seconds)." )
115+ return
116+ except ValueError as e :
117+ messagebox .showerror ("Error" , "Please enter a valid number." )
118+ return
119+ start_button .config (state = tk .DISABLED )
120+
121+ threading .Thread (target = run_in_background , args = (interval ,), daemon = True ).start ()
122+ else :
123+ update_weather ()
124+
125+ # Function to stop auto-updating
126+ def stop_notifier ():
127+ auto_mode .set (False )
128+ start_button .config (state = tk .NORMAL )
129+ stop_button .pack_forget ()
130+ go_back ()
131+
132+ def go_back ():
133+ weather_label .config (text = "" )
134+ city_label .pack (pady = 10 )
135+ city_entry .pack (pady = 5 )
136+ manual_radio .pack (anchor = tk .W , padx = 20 )
137+ auto_radio .pack (anchor = tk .W , padx = 20 )
138+ start_button .pack (pady = 10 )
139+ interval_label .pack_forget ()
140+ interval_entry .pack_forget ()
141+ stop_button .pack_forget ()
142+
143+ # gui setup
144+ def show_interval_entry ():
145+ if auto_mode .get ():
146+ interval_label .pack (pady = 5 )
147+ interval_entry .pack (pady = 5 )
148+ else :
149+ interval_label .pack_forget ()
150+ interval_entry .pack_forget ()
151+
152+ def toggle_stop_button ():
153+ if auto_mode .get ():
154+ stop_button .pack (pady = 5 )
155+ else :
156+ stop_button .pack_forget ()
157+
158+ if __name__ == '__main__' :
159+ city = "Surat"
160+ lat ,long = calculate_lat_long (city )
161+ if lat == None or long == None :
162+ print ('No city found' )
163+ exit (0 )
164+
165+ root = tk .Tk ()
166+ root .title ("Weather Notifier" )
167+ root .geometry ("550x500" )
168+ root .resizable (False , False )
169+
170+ # City Label and Entry
171+ city_label = tk .Label (root , text = "Enter your city:" )
172+ city_label .pack (pady = 10 )
173+ city_entry = tk .Entry (root , width = 30 ) # Define city_entry here
174+ city_entry .pack (pady = 5 )
175+
176+ # Weather Info Label
177+ weather_label = tk .Label (root , text = "" , font = ("Helvetica" , 10 ),justify = "left" )
178+ weather_label .pack (pady = 20 )
179+
180+ # Mode Selection: Manual or Automatic
181+ auto_mode = tk .BooleanVar ()
182+
183+ manual_radio = tk .Radiobutton (root , text = "On-click only" , variable = auto_mode , value = False )
184+ manual_radio .pack (anchor = tk .W , padx = 20 )
185+
186+ auto_radio = tk .Radiobutton (root , text = "Run after a fixed interval" , variable = auto_mode , value = True )
187+ auto_radio .pack (anchor = tk .W , padx = 20 )
188+
189+ # Interval Entry (only visible when interval mode is selected)
190+ interval_label = tk .Label (root , text = "Enter interval (seconds):" )
191+ interval_entry = tk .Entry (root , width = 10 )
192+
193+
194+
195+ auto_mode .trace_add ("write" , lambda * args : show_interval_entry ())
196+
197+ # Start Button
198+ start_button = tk .Button (root , text = "Start Notifier" , command = start_notifier )
199+ start_button .pack (pady = 10 )
200+
201+ # Stop Button (visible only when auto mode is active)
202+ stop_button = tk .Button (root , text = "Stop Notifier" , command = stop_notifier )
203+ stop_button .pack_forget ()
204+
205+ auto_mode .trace_add ("write" , lambda * args : toggle_stop_button ())
206+
207+ # Run the GUI loop
208+ root .mainloop ()
0 commit comments