Even Better Version with debounce hold timer to rest rapid button use and SOHO observatory
import os import sys import time import psutil import subprocess import threading import gpiozero # Import once at the top from PIL import Image, ImageDraw, ImageFont import spidev as SPI import requests from io import BytesIO # <-- Needed for icon image loading # Add parent directory to path to locate 'lib' sys.path.append("..") from lib import LCD_0inch96, LCD_1inch3, Gain_Param # Raspberry Pi pin config RST_0 = 24 DC_0 = 4 BL_0 = 13 BUS_0 = 0 DEVICE_0 = 0 RST_1 = 23 DC_1 = 5 BL_1 = 12 BUS_1 = 0 DEVICE_1 = 1 RST = 27 DC = 22 BL = 19 BUS = 1 DEVICE = 0 debounce_timer = None DEBOUNCE_DELAY = 0.3 # seconds debounce delay to batch rapid presses # Proxy setup (if needed, else remove or set to None) proxies = { "http": "http://192.168.1.1:3128", "https": "http://192.168.1.1:3128", } # OpenWeatherMap API details API_KEY = "YOUR OWN API HERE" ZIP_CODE = "YOUR OWN ZIP CODE HERE" UNITS = "imperial" # Weather cache to reduce API calls weather_cache = { "data": None, "last_updated": 0, # Unix timestamp "icon_code": None, "icon_image": None } # Doppler radar cache doppler_cache = { "image": None, "last_updated": 0 } doppler_frame_index = 0 # Track which frame to show # Frame caches and timers doppler_frames = [] last_doppler_fetch = 0 DOPPLER_URL = "https://radar.weather.gov/ridge/standard/KMUX_loop.gif" # GOES-18 product definitions GOES_PRODUCTS = [ { "name": "GeoColor", "url": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/psw/GEOCOLOR/GOES18-PSW-GEOCOLOR-600x600.gif" }, { "name": "GLM (Lightning)", "url": "https://cdn.star.nesdis.noaa.gov/GOES18/GLM/SECTOR/psw/EXTENT3/GOES18-GLM-PSW-EXTENT3-600x600.gif" }, { "name": "Air Mass RGB", "url": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/psw/AirMass/GOES18-PSW-AirMass-600x600.gif" }, { "name": "Sandwich RGB", "url": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/psw/Sandwich/GOES18-PSW-Sandwich-600x600.gif" }, { "name": "Day/Night Cloud", "url": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/psw/DayNightCloudMicroCombo/GOES18-PSW-DayNightCloudMicroCombo-600x600.gif" }, { "name": "Fire Temperature", "url": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/psw/FireTemperature/GOES18-PSW-FireTemperature-600x600.gif" }, { "name": "Band 02 - Red Visible", "url": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/psw/02/GOES18-PSW-02-600x600.gif" }, { "name": "Band 07 - Shortwave IR", "url": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/psw/07/GOES18-PSW-07-600x600.gif" }, { "name": "Band 08 - Water Vapor", "url": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/psw/08/GOES18-PSW-08-600x600.gif" }, { "name": "Band 13 - Clean IR", "url": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/psw/13/GOES18-PSW-13-600x600.gif" }, { "name": "Band 14 - IR Window", "url": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/psw/14/GOES18-PSW-14-600x600.gif" }, { "name": "SOHO EIT 171", "url": "https://soho.nascom.nasa.gov/data/LATEST/current_eit_171.gif" } ] # GOES-18 GIF cache containers (one per product) goes_dynamic_cache = [{} for _ in GOES_PRODUCTS] goes_dynamic_frame_index = [0 for _ in GOES_PRODUCTS] FRAME_DELAYS = { "default": 1, "animation": 0.1 } # Get IPv6 address def get_ipv6_address(): try: output = subprocess.check_output("ip -6 addr", shell=True).decode() for line in output.splitlines(): line = line.strip() if line.startswith("inet6") and "scope global" in line: return line.split()[1].split("/")[0] except Exception: pass return "No IPv6" # Fetch detailed weather info (dict with multiple fields) def get_detailed_weather(): now = time.time() if weather_cache["data"] and (now - weather_cache["last_updated"] < 300): return weather_cache["data"] try: response = requests.get( f"http://api.openweathermap.org/data/2.5/weather?zip={ZIP_CODE},us&appid={API_KEY}&units={UNITS}", timeout=5, proxies=proxies ) if response.status_code == 200: data = response.json() weather_cache["data"] = data weather_cache["last_updated"] = now # Download and cache icon if changed icon_code = data["weather"][0]["icon"] if icon_code != weather_cache["icon_code"]: icon_url = f"https://openweathermap.org/img/wn/{icon_code}@2x.png" try: icon_response = requests.get(icon_url, timeout=5, proxies=proxies) if icon_response.status_code == 200: icon_image = Image.open(BytesIO(icon_response.content)).convert("RGBA") weather_cache["icon_image"] = icon_image weather_cache["icon_code"] = icon_code else: weather_cache["icon_image"] = None weather_cache["icon_code"] = None except Exception: weather_cache["icon_image"] = None weather_cache["icon_code"] = None return data else: return None except Exception: return None # Fetch Doppler radar image, cached for 5 minutes def get_doppler_image(): now = time.time() if doppler_cache["image"] and (now - doppler_cache["last_updated"] < 300): return doppler_cache["image"] try: response = requests.get(DOPPLER_URL, timeout=5, proxies=proxies) if response.status_code == 200: gif = Image.open(BytesIO(response.content)) frames = [] try: while True: frame = gif.copy().convert("RGBA") frames.append(frame) gif.seek(gif.tell() + 1) except EOFError: pass # End of frames doppler_cache["image"] = frames doppler_cache["last_updated"] = now return frames except Exception: pass return None # --- Moon phase logic --- def get_moon_phase(): import math days_since_new = (time.time() / 86400.0) % 29.53 # lunar cycle ~29.53 days illumination = 0 phase_name = "New Moon" if days_since_new < 1.84566: phase_name = "New Moon" illumination = 0 elif days_since_new < 5.53699: phase_name = "Waxing Crescent" illumination = int((days_since_new - 1.84566) / (5.53699 - 1.84566) * 50) elif days_since_new < 9.22831: phase_name = "First Quarter" illumination = 50 elif days_since_new < 12.91963: phase_name = "Waxing Gibbous" illumination = int(50 + (days_since_new - 9.22831) / (12.91963 - 9.22831) * 50) elif days_since_new < 16.61096: phase_name = "Full Moon" illumination = 100 elif days_since_new < 20.30228: phase_name = "Waning Gibbous" illumination = int(100 - (days_since_new - 16.61096) / (20.30228 - 16.61096) * 50) elif days_since_new < 23.99361: phase_name = "Last Quarter" illumination = 50 elif days_since_new < 27.68493: phase_name = "Waning Crescent" illumination = int(50 - (days_since_new - 23.99361) / (27.68493 - 23.99361) * 50) else: phase_name = "New Moon" illumination = 0 return phase_name, illumination def draw_moon_icon(draw, center_x, center_y, radius, illumination): draw.ellipse((center_x - radius, center_y - radius, center_x + radius, center_y + radius), fill=(220, 220, 220)) shadow_color = (0, 0, 0) if illumination == 0: draw.ellipse((center_x - radius, center_y - radius, center_x + radius, center_y + radius), fill=shadow_color) elif illumination == 100: pass else: dark_frac = 1 - (illumination / 100.0) offset = int(radius * 2 * dark_frac) if illumination < 50: draw.ellipse((center_x + radius - offset, center_y - radius, center_x + radius * 2, center_y + radius), fill=shadow_color) else: draw.ellipse((center_x - radius * 2, center_y - radius, center_x - radius + offset, center_y + radius), fill=shadow_color) def get_goes_gif(index): now = time.time() product = GOES_PRODUCTS[index] cache = goes_dynamic_cache[index] if "image" in cache and (now - cache.get("last_updated", 0) < 300): return cache["image"] try: response = requests.get(product["url"], timeout=10, proxies=proxies) if response.status_code == 200: gif = Image.open(BytesIO(response.content)) frames = [] try: while True: frames.append(gif.copy().convert("RGBA")) gif.seek(gif.tell() + 1) except EOFError: pass cache["image"] = frames cache["last_updated"] = now return frames except Exception as e: print(f"Error fetching GOES [{product['name']}] GIF: {e}") return None # Setup displays gain = Gain_Param.Gain_Param() spi0 = SPI.SpiDev(BUS_0, DEVICE_0) spi1 = SPI.SpiDev(BUS_1, DEVICE_1) spi_main = SPI.SpiDev(BUS, DEVICE) spi0.open(BUS_0, DEVICE_0) spi1.open(BUS_1, DEVICE_1) spi_main.open(BUS, DEVICE) spi0.max_speed_hz = 10000000 spi1.max_speed_hz = 10000000 spi_main.max_speed_hz = 10000000 disp_0 = LCD_0inch96.LCD_0inch96(spi=spi0, spi_freq=10000000, rst=RST_0, dc=DC_0, bl=BL_0, bl_freq=1000) disp_1 = LCD_0inch96.LCD_0inch96(spi=spi1, spi_freq=10000000, rst=RST_1, dc=DC_1, bl=BL_1, bl_freq=1000) disp = LCD_1inch3.LCD_1inch3(spi=spi_main, spi_freq=10000000, rst=RST, dc=DC, bl=BL) disp.Init() disp_0.Init() disp_1.Init() disp.clear() disp_0.clear() disp_1.clear() disp.bl_DutyCycle(100) disp_0.bl_DutyCycle(100) disp_1.bl_DutyCycle(100) # Fonts FontXL = ImageFont.truetype("../Font/Font00.ttf", 28) FontLarge = ImageFont.truetype("../Font/Font00.ttf", 24) FontMid = ImageFont.truetype("../Font/Font00.ttf", 22) FontSmall = ImageFont.truetype("../Font/Font00.ttf", 16) FontTiny = ImageFont.truetype("../Font/Font00.ttf", 12) # Colors (RGB tuples) COLOR_IP = (0, 204, 204) COLOR_IPV6 = (102, 153, 204) COLOR_DATE = (76, 175, 80) COLOR_TIME = (255, 193, 7) COLOR_HEADER = (0, 153, 153) COLOR_WEATHER = (153, 204, 255) COLOR_CPU = (33, 150, 243) COLOR_TEMP = (100, 181, 246) COLOR_RAM = (255, 193, 7) COLOR_DISK = (156, 39, 176) COLOR_UP = (0, 188, 212) COLOR_DN = (77, 182, 172) COLOR_WPAD_HEADER = (0, 153, 153) COLOR_WPAD_USERS = (244, 67, 54) COLOR_WPAD_NONE = (117, 117, 117) # Button GPIO pins (adjust if needed) KEY1_PIN = 25 KEY2_PIN = 26 key1 = gpiozero.Button(KEY1_PIN) key2 = gpiozero.Button(KEY2_PIN) # Initial brightness percentage (10 to 100) brightness = 100 def update_brightness(new_brightness): global brightness brightness = max(10, min(100, new_brightness)) # clamp between 10 and 100 disp.bl_DutyCycle(brightness) disp_0.bl_DutyCycle(brightness) disp_1.bl_DutyCycle(brightness) print(f"Brightness set to {brightness}%") def key2_callback(): new_bright = brightness + 10 if new_bright > 100: new_bright = 10 update_brightness(new_bright) key2.when_pressed = key2_callback # State tracking for pages: 0=original,1=weather,2=moon,3=doppler current_page = 0 NUM_PAGES = 5 + len(GOES_PRODUCTS) # 5 original + new GOES pages key1_hold_timer = None key1_held = False HOLD_TIME_SECONDS = 3 def key1_hold_action(): global current_page, key1_held key1_held = True current_page = 0 print("Button held for 3 seconds: returning to page 0") def key1_pressed(): global key1_hold_timer, key1_held key1_held = False key1_hold_timer = threading.Timer(HOLD_TIME_SECONDS, key1_hold_action) key1_hold_timer.start() def update_display_after_debounce(): global debounce_timer, current_page debounce_timer = None print(f"Debounced update: Showing page {current_page}") # No need to call draw functions here because your main loop redraws every cycle, # but you could trigger a redraw if your code supports it. def key1_released(): global key1_hold_timer, key1_held, current_page, debounce_timer if key1_hold_timer: key1_hold_timer.cancel() key1_hold_timer = None if not key1_held: # Update page index immediately (on release) current_page = (current_page + 1) % NUM_PAGES print(f"Button released: page now {current_page}") # Cancel previous debounce timer if running if debounce_timer: debounce_timer.cancel() # Start debounce timer to delay display update (skip intermediate redraws) debounce_timer = threading.Timer(DEBOUNCE_DELAY, update_display_after_debounce) debounce_timer.start() key1.when_pressed = key1_pressed key1.when_released = key1_released # --- Original display functions --- def draw_top_screen(draw): ip = gain.GET_IP() ipv6 = get_ipv6_address() now = time.localtime() time_t = time.strftime("%H:%M:%S", now) time_D = time.strftime("%Y-%m-%d", now) draw.text((2, 0), 'IP: ' + ip, fill=COLOR_IP, font=FontSmall) draw.text((2, 18), ipv6, fill=COLOR_IPV6, font=FontTiny) draw.text((2, 36), 'DATE: ' + time_D, fill=COLOR_DATE, font=FontSmall) draw.text((2, 54), 'TIME: ' + time_t, fill=COLOR_TIME, font=FontSmall) def draw_middle_screen(draw, last_bytes): y = 0 spacing = 26 header_text = "ROSEVILLE WEATHER" w, _ = draw.textsize(header_text, font=FontSmall) draw.text(((disp.width - w) // 2, y), header_text, fill=COLOR_HEADER, font=FontSmall) y += spacing weather = get_detailed_weather() if weather: temp_f = int(round(weather["main"]["temp"])) humidity = int(weather["main"]["humidity"]) pressure_pa = weather["main"]["pressure"] * 100 pressure_inhg = pressure_pa / 3386.39 summary = f"{temp_f}°{humidity}% {pressure_inhg:.2f} inHg" else: summary = "Weather: Unavailable" w, _ = draw.textsize(summary, font=FontMid) draw.text(((disp.width - w) // 2, y), summary, fill=COLOR_WEATHER, font=FontMid) y += spacing wpad_header = "WPAD HEALTH" w, _ = draw.textsize(wpad_header, font=FontSmall) draw.text(((disp.width - w) // 2, y), wpad_header, fill=COLOR_HEADER, font=FontSmall) y += spacing cpu = int(psutil.cpu_percent(interval=None)) draw.text((2, y), f"CPU: {cpu}%", fill=COLOR_CPU, font=FontLarge) y += spacing temp = int(gain.GET_Temp()) draw.text((2, y), f"Temp: {temp}℃", fill=COLOR_TEMP, font=FontLarge) y += spacing ram = int(psutil.virtual_memory().percent) draw.text((2, y), f"RAM: {ram}%", fill=COLOR_RAM, font=FontLarge) y += spacing disk = int(psutil.disk_usage('/').percent) draw.text((2, y), f"Disk: {disk}%", fill=COLOR_DISK, font=FontLarge) y += spacing counters = psutil.net_io_counters() new_sent = counters.bytes_sent new_recv = counters.bytes_recv up = (new_sent - last_bytes[0]) * 8 / 1_000_000 dn = (new_recv - last_bytes[1]) * 8 / 1_000_000 draw.text((2, y), f"UP: {up:.2f}Mbps", fill=COLOR_UP, font=FontLarge) y += spacing draw.text((2, y), f"DN: {dn:.2f}Mbps", fill=COLOR_DN, font=FontLarge) return new_sent, new_recv def draw_wpad_status(draw): draw.text((2, 0), "WPAD Users:", fill=COLOR_WPAD_HEADER, font=FontMid) try: users = subprocess.check_output(['who']).decode().splitlines() usernames = sorted(set(line.split()[0] for line in users if line.strip())) display = ', '.join(usernames) if usernames else "None" except Exception as e: display = f"Error: {e}" color = COLOR_WPAD_USERS if display != "None" else COLOR_WPAD_NONE draw.text((2, 20), display, fill=color, font=FontMid) # --- Weather page drawing functions --- def draw_weather_top(draw): now = time.localtime() time_t = time.strftime("%H:%M:%S", now) date_t = time.strftime("%A, %b %d %Y", now) ip = gain.GET_IP() ipv6 = get_ipv6_address() draw.text((2, 0), "Detailed Weather", fill=COLOR_HEADER, font=FontSmall) draw.text((2, 18), f"IP: {ip}", fill=COLOR_IP, font=FontTiny) draw.text((2, 30), f"IPv6: {ipv6}", fill=COLOR_IPV6, font=FontTiny) draw.text((2, 44), date_t, fill=COLOR_DATE, font=FontTiny) draw.text((2, 58), time_t, fill=COLOR_TIME, font=FontTiny) def draw_weather_middle(draw): weather = get_detailed_weather() y = 0 spacing = 20 if weather: main = weather["main"] wind = weather.get("wind", {}) weather_desc = weather["weather"][0]["description"].capitalize() temp = main["temp"] feels_like = main.get("feels_like", temp) humidity = main["humidity"] pressure_pa = main["pressure"] * 100 pressure_inhg = pressure_pa / 3386.39 wind_speed = wind.get("speed", 0) wind_deg = wind.get("deg", 0) draw.text((2, y), f"Temp: {temp:.1f}°F", fill=COLOR_WEATHER, font=FontMid) y += spacing draw.text((2, y), f"Feels Like: {feels_like:.1f}°F", fill=COLOR_WEATHER, font=FontMid) y += spacing draw.text((2, y), f"Humidity: {humidity}%", fill=COLOR_WEATHER, font=FontMid) y += spacing draw.text((2, y), f"Pressure: {pressure_inhg:.2f} inHg", fill=COLOR_WEATHER, font=FontMid) y += spacing draw.text((2, y), f"Wind: {wind_speed} mph @ {wind_deg}°", fill=COLOR_WEATHER, font=FontMid) y += spacing draw.text((2, y), f"Condition: {weather_desc}", fill=COLOR_WEATHER, font=FontMid) else: draw.text((2, y), "Weather data unavailable", fill=COLOR_WPAD_NONE, font=FontMid) def draw_weather_bottom(image): draw = ImageDraw.Draw(image) weather = get_detailed_weather() if weather and "weather" in weather and len(weather["weather"]) > 0: condition = weather["weather"][0]["description"].capitalize() if weather_cache["icon_image"]: try: icon_img = weather_cache["icon_image"].resize((64, 64), Image.ANTIALIAS) x = (image.width - icon_img.width) // 2 y = (image.height - icon_img.height) // 2 - 10 image.paste(icon_img, (x, y), icon_img) font = FontTiny w, h = draw.textsize(condition, font=font) text_x = (image.width - w) // 2 text_y = y + icon_img.height + 2 draw.text((text_x, text_y), condition, fill=COLOR_WEATHER, font=font) except Exception: draw.text((10, 10), condition, fill=COLOR_WPAD_NONE, font=FontMid) else: draw.text((10, 10), condition, fill=COLOR_WPAD_NONE, font=FontMid) else: draw.text((10, 10), "No icon data", fill=COLOR_WPAD_NONE, font=FontMid) # --- Moon phase page --- def draw_moon_top(draw): now = time.localtime() time_t = time.strftime("%H:%M:%S", now) date_t = time.strftime("%A, %b %d %Y", now) ip = gain.GET_IP() ipv6 = get_ipv6_address() draw.text((2, 0), "Moon Phase", fill=COLOR_HEADER, font=FontSmall) draw.text((2, 18), f"IP: {ip}", fill=COLOR_IP, font=FontTiny) draw.text((2, 30), f"IPv6: {ipv6}", fill=COLOR_IPV6, font=FontTiny) draw.text((2, 44), date_t, fill=COLOR_DATE, font=FontTiny) draw.text((2, 58), time_t, fill=COLOR_TIME, font=FontTiny) def draw_moon_middle(draw): phase_name, illumination = get_moon_phase() w, h = draw.textsize(phase_name, font=FontLarge) draw.text(((disp.width - w) // 2, 10), phase_name, fill=COLOR_WEATHER, font=FontLarge) draw_moon_icon(draw, disp.width // 2, 80, 40, illumination) def draw_moon_bottom(draw): draw.text((2, 0), "Lunar illumination %", fill=COLOR_WPAD_HEADER, font=FontSmall) phase_name, illumination = get_moon_phase() draw.text((2, 18), f"{illumination}%", fill=COLOR_WEATHER, font=FontLarge) # --- Doppler radar page --- def draw_doppler_top(draw): now = time.localtime() date_str = time.strftime("%A, %b %d %Y", now) time_str = time.strftime("%H:%M:%S", now) ip = gain.GET_IP() ipv6 = get_ipv6_address() draw.text((2, 0), "Doppler Radar", fill=COLOR_HEADER, font=FontSmall) draw.text((2, 18), f"IP: {ip}", fill=COLOR_IP, font=FontTiny) draw.text((2, 30), f"IPv6: {ipv6}", fill=COLOR_IPV6, font=FontTiny) draw.text((2, 44), date_str, fill=COLOR_DATE, font=FontTiny) draw.text((2, 58), time_str, fill=COLOR_TIME, font=FontTiny) doppler_frame_index = 0 # Add this at the top level outside the loop #---GOES page------- def draw_doppler_middle(image): global doppler_frame_index frames = get_doppler_image() if frames: frame = frames[doppler_frame_index % len(frames)] resized = frame.resize((image.width, image.height), Image.ANTIALIAS) image.paste(resized, (0, 0), resized) doppler_frame_index += 1 else: draw = ImageDraw.Draw(image) msg = "Radar Image\nUnavailable" w, h = draw.textsize(msg, font=FontLarge) draw.text(((image.width - w) // 2, (image.height - h) // 2), msg, fill=COLOR_WPAD_NONE, font=FontLarge) def draw_doppler_bottom(draw): draw.text((2, 2), "Radar updated every 5 min", fill=COLOR_WPAD_NONE, font=FontTiny) def draw_goes_top(draw, label="GOES-18 Product"): now = time.localtime() date_str = time.strftime("%A, %b %d %Y", now) time_str = time.strftime("%H:%M:%S", now) ip = gain.GET_IP() ipv6 = get_ipv6_address() draw.text((2, 0), label, fill=COLOR_HEADER, font=FontSmall) draw.text((2, 18), f"IP: {ip}", fill=COLOR_IP, font=FontTiny) draw.text((2, 30), f"IPv6: {ipv6}", fill=COLOR_IPV6, font=FontTiny) draw.text((2, 44), date_str, fill=COLOR_DATE, font=FontTiny) draw.text((2, 58), time_str, fill=COLOR_TIME, font=FontTiny) def draw_goes_middle(image, index): global goes_dynamic_frame_index frames = get_goes_gif(index) if frames: frame = frames[goes_dynamic_frame_index[index] % len(frames)] resized = frame.resize((image.width, image.height), Image.ANTIALIAS) image.paste(resized, (0, 0), resized) goes_dynamic_frame_index[index] += 1 else: draw = ImageDraw.Draw(image) msg = "GOES GIF\nUnavailable" w, h = draw.textsize(msg, font=FontLarge) draw.text(((image.width - w) // 2, (image.height - h) // 2), msg, fill=COLOR_WPAD_NONE, font=FontLarge) def draw_goes_bottom(draw, product_name=None): text = "Updated every 5 min" if product_name: text = f"{product_name} - {text}" draw.text((2, 2), text, fill=COLOR_WPAD_NONE, font=FontTiny) # --- Main loop --- last_bytes_sent = 0 last_bytes_recv = 0 try: while True: if current_page == 0: # Original multi-display page image0 = Image.new("RGB", (disp_0.width, disp_0.height), (0, 0, 0)) draw0 = ImageDraw.Draw(image0) draw_top_screen(draw0) disp_0.ShowImage(image0) image = Image.new("RGB", (disp.width, disp.height), (0, 0, 0)) draw = ImageDraw.Draw(image) last_bytes_sent, last_bytes_recv = draw_middle_screen(draw, (last_bytes_sent, last_bytes_recv)) disp.ShowImage(image) image1 = Image.new("RGB", (disp_1.width, disp_1.height), (0, 0, 0)) draw1 = ImageDraw.Draw(image1) draw_wpad_status(draw1) disp_1.ShowImage(image1) elif current_page == 1: # Weather page image0 = Image.new("RGB", (disp_0.width, disp_0.height), (0, 0, 0)) draw0 = ImageDraw.Draw(image0) draw_weather_top(draw0) disp_0.ShowImage(image0) image = Image.new("RGB", (disp.width, disp.height), (0, 0, 0)) draw = ImageDraw.Draw(image) draw_weather_middle(draw) disp.ShowImage(image) image1 = Image.new("RGB", (disp_1.width, disp_1.height), (0, 0, 0)) draw_weather_bottom(image1) disp_1.ShowImage(image1) elif current_page == 2: # Moon phase page image0 = Image.new("RGB", (disp_0.width, disp_0.height), (0, 0, 0)) draw0 = ImageDraw.Draw(image0) draw_moon_top(draw0) disp_0.ShowImage(image0) image = Image.new("RGB", (disp.width, disp.height), (0, 0, 0)) draw = ImageDraw.Draw(image) draw_moon_middle(draw) disp.ShowImage(image) image1 = Image.new("RGB", (disp_1.width, disp_1.height), (0, 0, 0)) draw1 = ImageDraw.Draw(image1) draw_moon_bottom(draw1) disp_1.ShowImage(image1) elif current_page == 3: # Doppler radar page (page 3) # Refresh Doppler frames every 5 minutes if time.time() - last_doppler_fetch > 300 or not doppler_frames: doppler_frames = get_doppler_image() last_doppler_fetch = time.time() image0 = Image.new("RGB", (disp_0.width, disp_0.height), (0, 0, 0)) draw0 = ImageDraw.Draw(image0) draw_doppler_top(draw0) disp_0.ShowImage(image0) image = Image.new("RGB", (disp.width, disp.height), (0, 0, 0)) draw_doppler_middle(image) disp.ShowImage(image) image1 = Image.new("RGB", (disp_1.width, disp_1.height), (0, 0, 0)) draw1 = ImageDraw.Draw(image1) draw_doppler_bottom(draw1) disp_1.ShowImage(image1) elif current_page >= 4 and current_page < 4 + len(GOES_PRODUCTS): goes_index = current_page - 4 now = time.time() cache = goes_dynamic_cache[goes_index] # Refresh every 5 minutes if "last_updated" not in cache or (now - cache["last_updated"]) > 300: frames = get_goes_gif(goes_index) if frames: cache["image"] = frames cache["last_updated"] = now goes_dynamic_frame_index[goes_index] = 0 # reset animation index image0 = Image.new("RGB", (disp_0.width, disp_0.height), (0, 0, 0)) draw0 = ImageDraw.Draw(image0) draw_goes_top(draw0, GOES_PRODUCTS[goes_index]["name"]) disp_0.ShowImage(image0) image = Image.new("RGB", (disp.width, disp.height), (0, 0, 0)) frames = cache.get("image") if frames: frame = frames[goes_dynamic_frame_index[goes_index] % len(frames)] resized = frame.resize((image.width, image.height), Image.ANTIALIAS) image.paste(resized, (0, 0), resized) goes_dynamic_frame_index[goes_index] += 1 else: draw = ImageDraw.Draw(image) msg = "GOES GIF\nUnavailable" w, h = draw.textsize(msg, font=FontLarge) draw.text(((image.width - w) // 2, (image.height - h) // 2), msg, fill=COLOR_WPAD_NONE, font=FontLarge) disp.ShowImage(image) image1 = Image.new("RGB", (disp_1.width, disp_1.height), (0, 0, 0)) draw1 = ImageDraw.Draw(image1) draw_goes_bottom(draw1, GOES_PRODUCTS[goes_index]["name"]) disp_1.ShowImage(image1) if current_page in [3] + list(range(4, 4 + len(GOES_PRODUCTS))): time.sleep(FRAME_DELAYS["animation"]) else: time.sleep(FRAME_DELAYS["default"]) except KeyboardInterrupt: print("Exiting...") disp_0.clear() disp_1.clear() disp.clear() sys.exit()you need your own API key for the weather use and your own zip code and if you do not use a proxy disable that area